11

I want to patch the configuration for sharedSessionState to use MSSQL instead of memory. The default configuration from Sitecore.Analytics.Tracking.config renders as such:

Sitecore.Analytics.Tracking.config

<sharedSessionState defaultProvider="InProc">
  <providers>
    <clear/>
    <add name="InProc" type="System.Web.SessionState.InProcSessionStateStore"/>
  </providers>
  <manager type="Sitecore.Analytics.Tracking.SharedSessionState.SharedSessionStateManager, Sitecore.Analytics">
    <param desc="configuration" ref="tracking/sharedSessionState/config"/>
  </manager>
  <config type="Sitecore.Analytics.Tracking.SharedSessionState.SharedSessionStateConfig, Sitecore.Analytics">
    <param desc="maxLockAge">5000</param>
    <param desc="timeoutBetweenLockAttempts">10</param>
  </config>
</sharedSessionState>

The goal is to change the defaultProvider attribute and to replace the InProc provider. I attempt to do this with the following patch file:

z.Patch.config

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <tracking>
      <sharedSessionState defaultProvider="InProc">
        <patch:attribute name="defaultProvider" value="mssql" />
        <providers patch:instead="providers">
          <clear />
          <add name="mssql" type="Sitecore.SessionProvider.Sql.SqlSessionStateProvider,Sitecore.SessionProvider.Sql" connectionStringName="sharedsession" pollingInterval="2" compression="true" sessionType="shared"/>
        </providers>
      </sharedSessionState>
    </tracking>
  </sitecore>
</configuration> 

This patch results in the sharedSessionState correctly changing its defaultProvider, but the providers element is completely missing. At first I thought the patching engine couldn't find the right place to add the element, given that I changed its parent in the same configuration, but fiddling around I discovered that the following patch does include the providers element correctly:

z.ModifiedPatch.config

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <tracking>
      <sharedSessionState defaultProvider="InProc">
        <patch:attribute name="defaultProvider" value="mssql" />
        <providers customAttribute="whatever" patch:instead="providers">
          <clear />
          <add name="mssql" type="Sitecore.SessionProvider.Sql.SqlSessionStateProvider,Sitecore.SessionProvider.Sql" connectionStringName="sharedsession" pollingInterval="2" compression="true" sessionType="shared"/>
        </providers>
      </sharedSessionState>
    </tracking>
  </sitecore>
</configuration> 

The only change introduced in this modified patch is the addition of customAttribute="whatever" to the providers element.

Why is it seemingly not possible replace an element with an attribute-less element? Is there a better work-around in general than to add a bogus attribute?

3 Answers 3

11

Explanation

This is not an expected behavior. This is a defect in the Sitecore XML patching engine.

In the method Sitecore.Xml.Patch.XmlPatchHelper.InsertChild(), there's the following code:

case 'i': // this corresponds to patch:instead
  parent.InsertBefore(child, xmlNode);
  parent.RemoveChild(xmlNode);

So in your case, the new <providers> element is first inserted before the existing <providers> element, and then the old element is removed.

The tricky thing is the implementation of the method XmlNode.InsertBefore(newChild, refChild), which is a native XML method in .NET. This method has the following check:

if (newChild == refChild)
{
  return newChild;
}

So if the inserted node is the same in-memory object as the original node, nothing is going to happen. The new node will not be inserted, which means that the Sitecore code at parent.RemoveChild(xmlNode) is going to remove the <providers> node altogether.

I debugged through the Sitecore decompiled code and found that if both the original node and the new node have exactly the same data attributes (or no attributes at all), then object.ReferenceEquals(child, xmlNode) returns true. If the attributes differ, then it returns false, which means they are different objects and the replacement will succeed.

There's a defect somewhere in XmlPatchHelper that reuses the existing XML node object instead of creating a new one, which results in nothing being inserted, which in turn means the node being patched will be completely removed. This will only happen when using patch:instead.

I have registered this as a bug with Sitecore Support. If you want to track the status of the bug, use the reference number 94066.

Workarounds

It seems that currently you have the following options to work around this defect:

  • Don't delete the existing providers—they won't be used anyway.
  • First delete the whole node using <patch:delete /> and then add it from scratch.
  • Add a bogus attribute to the patch node so that it differs from the original node.

Hopefully, this will be fixed by Sitecore at some point.

8

You don't need to worry about removing the existing providers, instead just add another one and change the default provider to the new one:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:set="http://www.sitecore.net/xmlconfig/set/">
  <sitecore>
    <tracking>
      <sharedSessionState set:defaultProvider="mssql">
        <providers>
          <add name="mssql" type="Sitecore.SessionProvider.Sql.SqlSessionStateProvider,Sitecore.SessionProvider.Sql" connectionStringName="sharedsession" pollingInterval="2" compression="true" sessionType="shared"/>
        </providers>
      </sharedSessionState>
    </tracking>
  </sitecore>
</configuration>

Note the above is using set to change the default provider value, it reads a little cleaner than the patch:attribute syntax but both do the same thing. You can read more about it in the Include File Patching Facilities document.

The merged config will then look like this, all other elements will remain unchanged and Sitecore will use whatever provider is defined as the default:

<tracking>
  <sharedSessionState defaultProvider="mssql">
    <providers>
      <clear/>
      <add name="InProc" type="System.Web.SessionState.InProcSessionStateStore"/>
      <add name="mssql" type="Sitecore.SessionProvider.Sql.SqlSessionStateProvider,Sitecore.SessionProvider.Sql" connectionStringName="sharedsession" pollingInterval="2" compression="true" sessionType="shared"/>
    </providers>
    ...
  </sharedSessionState>
</tracking>
4
  • Thanks for the answer. In this specific example you are right - I don't need to remove the existing provider - but for other configurations it could have an impact. The answer doesn't address why the first configuration patch doesn't work. Commented Nov 1, 2016 at 14:14
  • "but for other configurations it could have an impact" -- I don't understand this comment. What's the it here? In this scenario, you're trying to add a child element to a parent element and change an attribute. Pretty simple use case. Is there another use case that you can provide that demonstrates the exact issue? I'm sincerely asking, cause I find @jammykams answer to fully answer this specific question. Commented Nov 1, 2016 at 14:25
  • It's quite rare to require an entire node to be deleted, generally you are supplementing Sitecore configuration. If you do then to delete, then @RichardSeal's answer to patch:delete first is generally the way to achieve this. This seems to be a limitation of the patching system.
    – jammykam
    Commented Nov 1, 2016 at 14:44
  • Also note the best practice from the document I linked to: Always Add New Provider Rather than Modify the Type of the Default
    – jammykam
    Commented Nov 1, 2016 at 14:49
5

This is happening because of the way you are trying to patch the providers section. If you want to delete the InProc provider and just add your mssql one you can do it like this:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <tracking>
      <sharedSessionState defaultProvider="InProc">
        <patch:attribute name="defaultProvider" value="mssql" />
        <providers>
          <add name="InProc" type="System.Web.SessionState.InProcSessionStateStore">
            <patch:delete />
          </add>
          <add name="mssql" type="Sitecore.SessionProvider.Sql.SqlSessionStateProvider,Sitecore.SessionProvider.Sql" connectionStringName="sharedsession" pollingInterval="2" compression="true" sessionType="shared"/>
        </providers>
      </sharedSessionState>
    </tracking>
  </sitecore>
</configuration> 

Notice that we are using patch:delete to remove the InProc provider, then just adding the mssql node into the list will add it to the merged config.

EDIT: Added a second option based on OP's comments

Another option would be to remove the providers element completely using patch:delete and then add it back in. I have used this many times with the <sites> list in the publish:end events. The config would look like this:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <tracking>
      <sharedSessionState defaultProvider="InProc">
        <patch:attribute name="defaultProvider" value="mssql" />
        <providers>
            <patch:delete />
        </providers>
        <providers>
          <add name="mssql" type="Sitecore.SessionProvider.Sql.SqlSessionStateProvider,Sitecore.SessionProvider.Sql" connectionStringName="sharedsession" pollingInterval="2" compression="true" sessionType="shared"/>
        </providers>
      </sharedSessionState>
    </tracking>
  </sitecore>
</configuration> 

As for which option you use will depend on how fine grained you want to be when removing child elements. If you want all gone and replaced with your updates, use option 2, if you want to only delete a few, option 1 is the best.

5
  • Thanks for the answer. The problem is that sharedSessionState has more children elements than the one I want to modify, so if I were to delete and add, I would have to copy all the configuration from the original patch file into my own, which is worse than a rogue attribute in my opinion. Commented Nov 1, 2016 at 12:38
  • 2
    What is the reasoning behind deleting them tho. As long as you patch the defaultProvider with the one you want to use, Sitecore will use that one. Having the others in the config wont affect that.
    – Richard Seal
    Commented Nov 1, 2016 at 12:39
  • That is correct, but it might not be true for other similar configurations. Commented Nov 1, 2016 at 12:41
  • I have updated to add a second option on how to do this.
    – Richard Seal
    Commented Nov 1, 2016 at 12:44
  • Thanks, the second option is definitely a better option in my opinion. Do you know why it doesn't work? Is it documented behavior or is it merely a bug? Commented Nov 1, 2016 at 14:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.