Monday, December 4, 2017

Exploring Sitecore xConnect: Working with Contacts and the xConnect Client API

Standard

Background

As I started exploring xConnect in XP 9, one of the questions I asked myself was how the change in the xDB contact and interaction model architecture would effect my existing Sitecore xDB implementations if we decided to upgrade. 

With this in mind, the focus of this post is on the changes to contact identification and updating contacts within Sitecore context, and what you need to know when you start working with the xConnect Client API in and outside of Sitecore context.

The xConnect documentation site was my initial point of reference, along with some guidance from Jason Wilkerson's series of posts

Working with Contacts

Identifying Contacts 

In XP 7.5 - 8.x, each xDB contact could be identified using a single, unique value. 

Your code looked like this:

 Tracker.Current.Session.Identify("menglish");  
 Tracker.Current.Contact.Identifiers.IdentificationLevel = ContactIdentificationLevel.Known;  

This changed in XP 9, as you now need to specify the source along with a unique value when identifying the contact.

 Tracker.Current.Session.IdentifyAs("corporateweb", "menglish");  
 //corporateweb is the source and menglish is the identifier.   

In XP 9, each contact can have multiple identifiers and sources within the new model. The magic lies in the ability to identify and merge contacts from all different sources together into a single contact.

Omnichannel contact identification and merging is a powerful thing!



Updating Contacts

In XP 7.5 - 8.x, updating contact information in xDB was achieved by using the Tracker Contact (Tracker.Current.Contact), and calling GetFacet using the Interface of the type and passing in the name of the Facet. 

Your code looked like this:

 var existingContact = Tracker.Current.Contact;  
                            
 var personalFacet = existingContact.GetFacet<IContactPersonalInfo>("Personal");  
   
 personalFacet.FirstName = "Martin";  
 personalFacet.Surname = "English";  

This code will still run in XP 9, but it will no longer save the information to xDB. The update will only persist within session.

In XP 9, you need to use the xConnect Client API in order to update contact information.

 using (Sitecore.XConnect.Client.XConnectClient client = Sitecore.XConnect.Client.Configuration.SitecoreXConnectClientConfiguration.GetClient())  
 {  
      try  
      {  
           var webContactIdentifier = Tracker.Current.Contact.Identifiers.FirstOrDefault(t => t.Source == "corporateweb")?.Identifier;  
           var existingContact = client.Get<Sitecore.XConnect.Contact>(new IdentifiedContactReference("corporateweb", webContactIdentifier), new Sitecore.XConnect.ContactExpandOptions(PersonalInformation.DefaultFacetKey));  
   
           if (existingContact != null)  
           {  
                var personalFacet = existingContact.GetFacet<PersonalInformation>() ?? new PersonalInformation();  
   
                personalFacet.FirstName = "Martin";  
                personalFacet.LastName = "English";  
   
                client.SetFacet(existingContact, PersonalInformation.DefaultFacetKey, personalFacet);  
   
                client.Submit();  
           }  
      }  
      catch (XdbExecutionException ex)  
      {  
           //Oops, something went wrong  
      }  
 }  

Some important things that you need to be aware of:

  • The xConnect Contact and Tracker Contact models are different. 
  • The legacy facet classes are available in XP 9, so your existing code won't break. 
  • When you update your Tracker facet's in session, the update will persist throughout session, but won't save to xDB.
  • On session end, the Tracker contact data is run through a series of conversion pipelines where it ends up in xConnect. 
  • When the web contact returns to the site, the Tracker contact is hydrated through another set of conversion pipelines using the contact's data stored in xConnect.
  • If you have been updating xDB contact data using the Tracker Contact and calling GetFacet, you will need to update your code to use the xConnect Client API in order to update contact information. 

Working with the xConnect Client API

xConnect Client API within Sitecore Context

If you are working within a Sitecore Context, using the xConnect client is really straightforward. You don't have to worry about endpoints or certificates, as all that is abstracted. An example of this is shown above.

xConnect Client API outside Sitecore Context

Unsecured Client Connection

The example code that Jason has on GitHub requires an untrusted client connection in order to work.

Example:

 private static XConnectClient GetClient()  
 {  
   var config = new XConnectClientConfiguration(new XdbRuntimeModel(CollectionModel.Model), new Uri("https://sc90.xconnect"), new Uri("https://sc90.xconnect"));  
     
   try  
   {  
     config.Initialize();  
   }  
   catch (XdbModelConflictException ex)  
   {  
     Console.WriteLine(ex.Message);  
     throw;  
   }  
   
    return new XConnectClient(config);  
 }  

In order to run this, you need to disable these two xml files: sc.XConnect.Security.EnforceSSLWithCertificateValidation.xml and sc.XConnect.Security.EnforceSSL.xml located at:  [location of your xConnect instance]\App_data\config\sitecore\CoreServices

If you don't, you will receive the following Sitecore.XConnect.XdbCollectionUnavailableException "The HTTP response was not successful: Unauthorized".

Making this type of adjustment is fine if you are writing some POC code, but it is obviously not recommended as you start writing code for your customers.

Secured Client Connection

In order to establish a trusted client connection, you need to add the security certificate info to the request.

The most important thing you will need is the xConnect client certificate thumbprint that is found in the validateCertificateThumbprint setting in the your xConnect AppSettings.config, located at [location of your xConnect instance]\App_Config\ or in the ConnectionStrings.config of your Sitecore instance. The "FindValue" part of each xConnect Connection String contains this value.

For example:

  <add name="xconnect.collection.certificate" connectionString="StoreName=My;StoreLocation=LocalMachine;FindType=FindByThumbprint;FindValue=ADC6D07F383B2E116CC7510F4681EA34EE822F22" />  
    

To see this value within the certificate itself, you can navigate to it within your Personal Certificates store, shown below:

Using this thumbprint, we can make a secure xConnect client connection using the following code sample:

 var certThumbprint = "adc6d07f383b2e116cc7510f4681ea34ee822f22";  
 var xConnectUrl = "https://sc90.xconnect";  
   
 var options = CertificateWebRequestHandlerModifierOptions.Parse($"StoreName=My;StoreLocation=LocalMachine;FindType=FindByThumbprint;FindValue={certThumbprint}");  
 var certificateModifier = new CertificateWebRequestHandlerModifier(options);  
   
 var clientModifiers = new List<IHttpClientModifier>();  
 var timeoutClientModifier = new TimeoutHttpClientModifier(new TimeSpan(0, 0, 20));  
   
 clientModifiers.Add(timeoutClientModifier);  
   
 var collectionClient = new CollectionWebApiClient(new Uri($"{xConnectUrl}/odata"), clientModifiers, new[] { certificateModifier });  
 var searchClient = new SearchWebApiClient(new Uri($"{xConnectUrl}/odata"), clientModifiers, new[] { certificateModifier });  
 var configurationClient = new ConfigurationWebApiClient(new Uri($"{ xConnectUrl }/configuration"), clientModifiers, new[] { certificateModifier });  
 var config = new XConnectClientConfiguration(new XdbRuntimeModel(CollectionModel.Model), collectionClient, searchClient, configurationClient);  
   
 try  
 {  
      config.Initialize();  
 }  
 catch (Exception e)  
 {  
      Console.WriteLine(e);  
      throw;  
 }  
   
 return new XConnectClient(config);  

Wrap Up

I hope that this post has helped you understand some of the contact changes that xConnect presents us with, and also provides enough crumbs to get you started using the xConnect Client API.

Happy Exploring!


Sunday, October 15, 2017

Sitecore xDB: A Mechanic's Guide to Personalization Testing Troubleshooting

Standard

Background

On my last few projects, I have experienced first-hand how marketers have leveraged the true power of Content Testing in the Experience Platform to truly gain some fantastic insights so that they can successfully optimize content in order to deliver an improved contextual customer experience.

Most of the tests I have experienced have been personalization-based, and I have helped various teams troubleshoot some glitches that have popped up along the way. I guess you can call me the "content testing mechanic".




In this post, I will provide some insight from my experiences to help other developers who face similar issues, get the issues resolved quickly.

Nuts and Bolts

The main entry point into content testing for users is workflow, and so the assumption is that you have some type of workflow in place to successfully launch tests.

For more information on this, please review Sitecore's documentation on Adding content testing to a workflow  as well as Jonathan Robbins' Sitecore 8 Content Testing post.

With a workflow in place, the following things happen under the covers when you launch a new test:

  • A new test item is created that contains all the information about the test. This can be found at this location: /sitecore/system/Marketing Control Panel/Test Lab.
  • The Final Renderings XML field will be updated with specific testing attributes that contain values with test reference information*.
  • The item that you are testing and the new test item will be published to the web database (based on workflow action).

* To view this, you will need to enable raw values and standard fields in the “View” section of the “View” tab

An example of the Final Renderings XML looks something like the below:


Looking at this XML, you will see that it contains a p:t attribute. This denotes a personalization test reference. More on this further down.

Springs that Pop Out

If after starting a personalization test, personalization on the component(s) that you are testing stops working and you don't see any data appearing in your Test Result dialogue, the most common error in your Sitecore logs will be the following "Evaluation of condition failed. Rule item ID: Unknown, condition item ID exception":

 ERROR Evaluation of condition failed. Rule item ID: Unknown, condition item ID: {4888ABBB-F17D-4485-B14B-842413F88732}  
 Exception: System.NullReferenceException  
 Message: Object reference not set to an instance of an object.  
 Source: Sitecore.ContentTesting  
   at Sitecore.ContentTesting.Pipelines.RenderingRuleEvaluated.TestingRule.Process(RenderingRuleEvaluatedArgs args)  
   at (Object , Object[] )  
   at Sitecore.Pipelines.CorePipeline.Run(PipelineArgs args)  
   at Sitecore.Rules.RuleList`1.Run(T ruleContext, Boolean stopOnFirstMatching, Int32& executedRulesCount)  
   

At first glance, you might think that this error is simply because of the condition item with ID {4888ABBB-F17D-4485-B14B-842413F88732} that is not published to the web database.

 Unfortunately, this isn't the case.

Under the Hood

After working through Sitecore's testing code, the exception occurs while Sitecore evaluates one of the conditions associated with the personalization rule.

Sitecore.ContentTesting.Pipelines.RenderingRuleEvaluated.TestingRule                 Sitecore.Rules.Evaluate


The invocation that fails and causes the NullReferenceException is the 'rule.Condition.Evaluate(ruleContext)' where:

rule - is the Rule object instantiated from the rule XML definition ( from the presentation details )
Condition - is the root condition definition from the rule XML definitions
ruleContext - is the object containing additional data for the rule evaluation such as:
  • Item reference: this should be the page definition item. 
  • Test reference: this should be the test associated with the rendering.
  • The current MVC rendering object reference

My analysis determined that the most common cause of the error is due to old tests that are still part of the content item's configuration, that are either not stopped correctly, inactive or have been removed.

Fixing the Issue

The fix is to remove the bad/old test references from the item in question's Final Renderings XML field. 

My process to do this is the following:

  • Determine what item is throwing the testing exception.
  • Enable raw values and standard fields in the “View” section of the “View” tab.
  • Copy the Final Renderings XML value of the item and format it so that it is easy to read. This site does a nice job: https://www.freeformatter.com/xml-formatter.html
  • Paste you’re the XML into Visual Studio or another editor.
  • Locate the attributes in the XML that have a s:pt and remove the attributes.
  • Copy and paste the updated XML back into the item's Final Renderings field.
  • Save and publish.

After this, the errors will stop appearing in your logs. You will however need to launch your test again.

Final Gotcha

The exact same "Evaluation of condition failed. Rule item ID: Unknown, condition item ID" exception error mentioned above could also occur if content testing has been disabled.

In XP 8.1 and later, it is disabled when the ContentTesting.AutomaticContentTesting.Enabled setting is set to false in the App_Config\Include\ContentTesting\Sitecore.ContentTesting.config file.

This is a bit obscure, as one would think that there would be some other messaging in the logs indicating that this setting has been disabled.

Testing Diagnostics Page for your Toolbox

I have seen some cases where the ribbon of the Optimization tab shows a different number of active tests if compared to the active test list. An example of this is shown below:



One of the first things that you can try is to rebuild your sitecore_testing_index index. If this doesn't help, you can use the diagnostics page below to help troubleshoot the issue.

The page output will look similar to this:

~~~~~~~~~~~~~~

Active filtered tests:
Test search result object checked for Item: sitecore://{703EED9B-C574-4310-AC47-EBCCB651F67E}?lang=en&ver=2, Test Item: sitecore://master/{57807f6f-a836-4132-8b2b-48124b0c4031}?lang=en&ver=1, Is Running: True, Is Cancelled: False

Test search result object checked for Item: sitecore://{0CA61CF3-D35A-4FE7-8CD6-90CF8F61179A}?lang=en&ver=9, Test Item: sitecore://master/{cb771932-d06d-4f42-9392-483fab3cbc1c}?lang=en&ver=1, Is Running: True, Is Cancelled: False
Configuration is null

Test search result object checked for Item: sitecore://{8C65AEB8-90A2-4348-BCDB-D8AB6CBA5974}?lang=en&ver=1, Test Item: sitecore://master/{0b298164-fcc5-4803-acda-5a321e8c2797}?lang=en&ver=1, Is Running: True, Is Cancelled: False

1 {57807F6F-A836-4132-8B2B-48124B0C4031}
2 {0B298164-FCC5-4803-ACDA-5A321E8C2797}
Active tests:
1 sitecore://master/{57807f6f-a836-4132-8b2b-48124b0c4031}?lang=en&ver=1
2 sitecore://master/{cb771932-d06d-4f42-9392-483fab3cbc1c}?lang=en&ver=1
3 sitecore://master/{0b298164-fcc5-4803-acda-5a321e8c2797}?lang=en&ver=1

~~~~~~~~~~~~~~~

The diagnostic output example above shows us that the issue lies with the test item with ID {cb771932-d06d-4f42-9392-483fab3cbc1c}.

To fix the issue, you will need to locate the test item with that ID and either set its "Is Running" field value to "No" or simply delete that item.

Make sure that these updates get published to your web database.


Thursday, September 7, 2017

Sitecore xDB: Goal Conversion Sweet Spot Demo

Standard

Background

This question seems to keep coming up during client engagements:

Where and how can we get the goals that our visitor's have converted during their interaction with our website so that we can send this information to our external (CRM) system?

I decided to put together a small, working example to help answer this question.

Purpose

The purpose of this code is to demonstrate how to obtain goals that were triggered during a visitor's interaction, after their session has ended.

So, why is this useful?

This is a useful sweet spot as this data can be sent to an external system where it can be used to help marketers by informing them of what customer’s and leads are doing on their website.

Full source code is available from my GitHub repository: https://github.com/martinrayenglish/GoalConversions.Demo





Thursday, August 31, 2017

Sitecore xDB - Adding Custom Data to Outcomes and Using it for Personalization

Standard
In a recent Digital Strategist MVP Webinar hosted by Chris Nash, Chris made a quick mention of a custom values property on outcomes that was available to developers to store custom data associated with the outcome.

Having not known about this Easter egg prior, I looked into it straight away.

My research revealed that there wasn't any documentation or an example on the web, so I thought that I would take the opportunity to demonstrate the usage in a real-world implementation.

Use Case

One of the objectives in our Xccelerate roadmap was the ability to personalize based on a contact's previous purchase. So for example; "If a visitor has purchased a product in the last 10 days, let's show them a CTA of a related product". The obvious objective was to drive the contact directly into the purchase funnel, increasing the possibility of another conversion.

In our configuration, we had already created a Purchase outcome item and were capturing monetary value, so it was just a matter of attaching the purchased line items to the outcome and building a new condition.

It is important to note that the "Monetary Value Applicable" checkbox must be checked on the Outcome item in Sitecore so that the values will show up in the various reporting dashboards.



Adding Custom Data to the Outcome

The code to achieve this is pretty straightforward. In our case, I added the logic to a location where I had a list of line items available that a visitor had just purchased.

The method accepts a monetary value and a list of key value pairs containing data that I would attach to my registered outcome. In my usage, I stored all the product SKUs, along with the quantity purchased for each line.

1:            public void RecordPurchaseOutcome(decimal monetaryValue, List<KeyValuePair<string, string>> customValues = null)  
2:            {       
3:                 var id = ID.NewID;  
4:                 var interactionId = ID.Parse(Tracker.Current.Interaction.InteractionId);  
5:                 var contactId = ID.Parse(Tracker.Current.Contact.ContactId);  
6:                 var definitionId = new ID(ItemConstants.ProductPurchaseOutcomeItem);  
7:    
8:                 var outcome = new ContactOutcome(id, definitionId, contactId)  
9:                 {  
10:                      DateTime = DateTime.UtcNow.Date,  
11:                      MonetaryValue = monetaryValue,  
12:                      InteractionId = interactionId,  
13:                 };  
14:    
15:                 if (customValues != null && customValues.Any())  
16:                 {  
17:                      foreach (var customValue in customValues)  
18:                      {  
19:                           outcome.CustomValues[customValue.Key.ToUpperInvariant()] = customValue.Value;  
20:                      }  
21:                 }  
22:    
23:                 Tracker.Current.RegisterContactOutcome(outcome);  
24:            }  

The id of the outcome item on line 6 displayed as ItemConstants.ProductPurchaseOutcomeItem is unique to my implementation.

After a contact's session ended, the custom data was stored in the MongoDB Outcome collection as follows:


Personalization using the Outcome's Custom Data

The final piece of the puzzle was to build the personalization condition that could pull out the custom data from the contact's recorded outcome, based on a time frame.

I created the condition item in the Outcomes element folder at this location: /sitecore/system/Settings/Rules/Definitions/Elements/Outcomes.

The rule text was set to the following:

 where the current contact has registered the [OutcomeId,Tree,root=/sitecore/system/Marketing Control Panel/Outcomes,specific] outcome with a custom data key that is case-insensitively equal to [CustomData,,,value] within the last [days,Integer,,number] day(s)  



This is the code that powered the condition, accessing the recorded custom data in the contact's outcome, and determined if it fell within a day range:

1:    public class CustomDataOutcomeRegisteredWithinLastDaysCondition<T> : WhenCondition<T> where T : RuleContext  
2:    {  
3:      private OutcomeManager _outcomeManager;  
4:    
5:      public string OutcomeId { get; set; }  
6:      public string Days { get; set; }  
7:      public string CustomData { get; set; }  
8:    
9:      protected override bool Execute(T ruleContext)  
10:      {  
11:        Assert.ArgumentNotNull(ruleContext, "ruleContext");  
12:        Assert.IsNotNull(Tracker.Current, "Tracker.Current is not initialized");  
13:        Assert.IsNotNull(Tracker.Current.Session, "Tracker.Current.Session is not initialized");  
14:    
15:        Guid result;  
16:        if (!Guid.TryParse(OutcomeId, out result))  
17:        {  
18:          Log.Debug(string.Format("Specified outcome [{0}] was not a valid Guid", OutcomeId));  
19:          return false;  
20:        }  
21:    
22:        _outcomeManager = Factory.CreateObject("outcome/outcomeManager", true) as OutcomeManager;  
23:    
24:        if (_outcomeManager != null)  
25:        {  
26:          int pastDays;  
27:          var validDays = int.TryParse(Days, out pastDays);  
28:    
29:          if (!validDays)  
30:          {  
31:            return false;  
32:          }  
33:    
34:          var targetDate = DateTime.Today.AddDays(-pastDays);  
35:          var pastOutcomes = _outcomeManager.GetForEntity<IOutcome>(Tracker.Current.Contact.ContactId.ToID(), result.ToID());  
36:    
37:          foreach (var outcome in pastOutcomes)  
38:          {  
39:            if (outcome.DateTime >= targetDate)  
40:            {  
41:              return outcome.CustomValues[CustomData.ToUpperInvariant()] != null;  
42:            }  
43:          }  
44:    
45:          return false;  
46:        }  
47:    
48:        return false;  
49:      }  
50:    }  

After my rule and code were added to Sitecore, I was able to apply the freshly minted condition to my component:



The end result was the ability to personalize based on a previous purchase that a contact had made within the last x number of days.

In my example, I personalized the content of my component if the contact had purchased a Large, Hot Penne Pasta and Meatballs Tray within the last 10 days.

Monday, July 31, 2017

Sitecore SQL Session State Provider: What you need to know

Standard

Background 

While working with Sitecore Support to troubleshoot a SQL session issue that we encountered on a high-traffic, scaled,  Sitecore environment running Sitecore 8.1 Update 2, we discovered that the root cause of the issue was a connection leaking bug in the SessionStateStoreProvider that causes unnecessary load on SQL server making it unresponsive.

The purpose of this post is to arm you with the information that you need to implement a stable SQL Session State in your Sitecore deployment.

Symptoms 

When the issue / outage occurred, the exceptions in the Sitecore logs where the following:

 Exception: System.Data.SqlClient.SqlException  
 Message: A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: TCP Provider, error: 0 - No such host is known.)  
 Source: .Net SqlClient Data Provider  
  at System.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, UInt32 waitForMultipleObjectsTimeout, Boolean allowCreate, Boolean onlyOneCheckConnection, DbConnectionOptions userOptions, DbConnectionInternal& connection)  
  at System.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, TaskCompletionSource`1 retry, DbConnectionOptions userOptions, DbConnectionInternal& connection)  
  at System.Data.ProviderBase.DbConnectionFactory.TryGetConnection(DbConnection owningConnection, TaskCompletionSource`1 retry, DbConnectionOptions userOptions, DbConnectionInternal oldConnection, DbConnectionInternal& connection)  
  at System.Data.ProviderBase.DbConnectionInternal.TryOpenConnectionInternal(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource`1 retry, DbConnectionOptions userOptions)   
  at System.Data.SqlClient.SqlConnection.TryOpenInner(TaskCompletionSource`1 retry)  
  at System.Data.SqlClient.SqlConnection.TryOpen(TaskCompletionSource`1 retry)  
  at System.Data.SqlClient.SqlConnection.Open()  
  at System.Data.SqlClient.SqlConnection.Open()  
  at Sitecore.SessionProvider.Sql.SqlSessionStateStore.UpdateItemExpiration(Guid application, String id) 
  at Sitecore.SessionProvider.Sql.SqlSessionStateProvider.ResetItemTimeout(HttpContext context, String id)  
  at System.Web.SessionState.SessionStateModule.BeginAcquireState(Object source, EventArgs e, AsyncCallback cb, Object extraData)  
  at System.Web.HttpApplication.AsyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()  
  at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)  
 Nested Exception  
 Exception: System.ComponentModel.Win32Exception  
 Message: No such host is known  
Our New Relic Application monitoring system reported that the GetExpiredItemExclusive SQL stored procedure that was running against the session database was the most time consuming and was responsible for the highest throughput between our Sitecore and SQL server.

We discovered that once the execution of the stored procedure got above the 10,000 calls per minute range, the application would start having trouble and would eventually stop responding.

Root Cause

It was determined that .NET was naturally creating a number of session state provider objects, and during high traffic periods, the number got so large that it caused too much load on the SQL server and eventually caused the application to stop responding.

Stabilizing Session State

The Patch 

Sitecore Support issued us with a patch and noted that the issue was fixed in 8.2 Update 2 on. 

From a high level, the change involved Sitecore using their own factory and creating session state objects manually. 

The issue was registered as bug #98800. It's important to note that all prior versions will require a ticket to request the patch. 

We implemented the patch by following these steps:
1) Put the attached 'Sitecore.Support.98800.dll' assembly to the /bin folder of the website.
2) Changed session state provider type from
type="Sitecore.SessionProvider.Sql.SqlSessionStateProvider, Sitecore.SessionProvider.Sql"
to
type="Sitecore.Support.SessionProvider.Sql.SqlSessionStateProvider, Sitecore.Support.98800"

The change was made in both the Web.Config and Sitecore.Analytics.Tracking.config

Not Stable Yet

About 3 days later, our site was brought down to it's knees again. New Relic showed that the GetExpiredItemExclusive SQL stored procedure calls were well above the 10,000 calls per minute range.

Configuration Update

Working with Sitecore Support again, we increased the Session Provider polling interval from the default 2 seconds to 60 seconds and also increased the SQL connection timeout to 300 seconds.

The polling interval is basically the number of seconds to check the Session database for expired sessions. Under the covers, this would execute the GetExpiredItemExclusive SQL stored procedure.

The final configurations looked like these:

Sitecore.Analytics.Tracking.config

 <add  
  name="mssql"  
  type="Sitecore.Support.SessionProvider.Sql.SqlSessionStateProvider, Sitecore.Support.98800"  
  connectionStringName="session"  
  pollingInterval="60"  
  compression="true"  
  sessionType="shared"/>  
Web.Config

 <add   
  name="mssql"   
  type="Sitecore.Support.SessionProvider.Sql.SqlSessionStateProvider, Sitecore.Support.98800"   
  connectionStringName="session"   
  pollingInterval="60"   
  compression="true"   
  sessionType="private"/>  
ConnectionStrings.config

 <add name="session"   
    connectionString="user id=xxx;password=xxx;Data Source=xxx,1433;Database=Sessions;MultiSubnetFailover=TRUE; Connection Timeout=300" />  

Status

With the patch in place, and the final configuration updates, the application has been stable and has survived extremely high traffic days. 

An example of one of these days: 40,000 requests per minute, 7500 simultaneous users and 142,000 page views per hour.

Takeaways

If you intend to use SQL Session State for your Sitecore implementation, and are running a version of Sitecore prior to 8.2 Update 2, you need to create a ticket with Sitecore support to request the patch.

After this, it's critical that you increase your polling interval configuration from the default 2 seconds to something higher like we did. 60 seconds seems to be the perfect number.

If you have any questions, feel free to submit a comment and I will help you out to the best of my knowledge about this issue.

Monday, June 19, 2017

How to trigger xDB Pattern Cards using jQuery AJAX with Sitecore MVC

Standard

Background 

Sitecore makes it easy for content marketers to assign Profile Cards to pages when implementing a behavioral profile strategy, but more often than not, there are areas of today's modern Sitecore sites where this is a bit challenging.

For the same reason that you may want to trigger Goals or Outcomes using jQuery AJAX, an event driven way to trigger Pattern Cards would be equally useful.

My interest to achieve this was sparked while working on a eCommerce site where we implemented many, many dynamic lightboxes / modals during the ordering flow where we wanted to implement a part of our behavioral profiling strategy.

NOTE: The P’s of Sitecore Personalization can by somewhat tricky to understand, so if you need to brush up on the lingo before reading further, I suggest that you take a look at Mike Shaw's post on Profile Cards and Other P’s of Sitecore Personalization.


Architecture 

I am a fan of using Sitecore.Services.Client, and as I explained in this post, making your controllers xDB / session aware is pretty easy. If you prefer, you can most certainly use a regular MVC controller.

When thinking through the architecture, I wanted to be able to trigger multiple pattern cards using percentages as it would provide the most flexibility.

I needed to be sure that I had the right score calculation checks in place, as I wanted the same results as if the cards were triggered from a page where they had been assigned using the Content or Experience Editor.

You will see in the code that follows where I needed to ensure that the percentages sum had to be equal to 100 if there were multiple cards, or if there were more than one card without an assigned percentage to any of them, the percentages needed to be distributed evenly.

Pattern Card Model


Controller Action

This controller action may be a bit long-winded, but I wanted to demonstrate the flow of logic, from top to bottom.


Trigger using jQuery

Finally, here is an example button click event where we post an array of Pattern Card objects to our controller action.

Note, if you are using Sitecore.Services.Client or WebAPI, there is a known issue that will force you to change your data to be a single anonymous object instead of a raw array.

Thursday, June 8, 2017

The easy way to enable xDB tracking for Sitecore.Services.Client and Web API

Standard
There are several posts on the web that talk about the fact that Sitecore.Services.Client (SSC) and Web API don't allow xDB tracking due to the fact that they are session-less by default.

In this older post, Pavel explains the reason for this, and a provides a solution to make SSC or Web API session aware: http://jockstothecore.com/xdb-tracking-the-untrackable-part-1

In my post, I will demonstrate how to do this in 5 lines of code.

FXM Is Your Huckleberry

As described by the article, the key is to give the target route a session aware handler right after the SSC route (or Web API route) has been registered in the initialize pipeline.

FXM has a BeaconSessionRouteHandler already built in, because its SSC controllers track activity in xDB from external sites using the magical Beacon script.

So, using what FXM already gives you, all you need is the following "Session State Enabler" Processor in the initialize pipeline, right after the ServicesWebApiInitializer processor.

 Code

 using System.Web.Routing;  
   
 using Sitecore.FXM.Service.Handler;  
 using Sitecore.Pipelines;  
   
 namespace MyProject.Pipelines.Initialize  
 {  
   public class EnableEntityServiceSessionStateProcessor  
   {  
     public void Process(PipelineArgs args)  
     {  
       var route = RouteTable.Routes["EntityService"] as Route;  
   
       if (route != null)  
       {  
         route.RouteHandler = new BeaconSessionRouteHandler();  
       }  
     }  
   }  
 }  
   

Config

 <configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">  
  <sitecore>  
   <pipelines>  
    <initialize>  
     <processor type="MyProject.Pipelines.Initialize.EnableEntityServiceSessionStateProcessor, MyProject"  
     patch:after="processor[@type='Sitecore.Services.Infrastructure.Sitecore.Pipelines.ServicesWebApiInitializer, Sitecore.Services.Infrastructure.Sitecore']" />  
    </initialize>  
  </sitecore>  
 </configuration>  

 That's it! You can now perform xDB tracking in your SSC or Web API controllers.