Stefan Holm Olsen

Fix remote cache invalidation for Commerce sites on localhost

When developing an Episerver Commerce solution, I run two websites projects side-by-side: Commerce Manager and the CMS website. They each hold their own cache, which should be closely synchronized whenever some data is changed (read my blog post about it here).

This rarely works for me, out-of-the-box, because the out-of-the-box configuration fits badly in most development environments, because of network restrictions and firewalls blocking multicast networking.

The following is an easy, simple and elegant solution.

The issue faced

When setting up a new empty site there are no event providers set up. This means that the site only works properly when run on a single web app instance. This is fine for running a CMS-only site locally, but unusable in all other scenarios. So, we need to choose and add a configuration before deploying.

When cloning the Quicksilver or Foundation sample sites, some configuration is included, but not actually enabled. The default included configuration is based on UDP packets and a multicast group IP-address. This is great when it works, but many networks block this, including clouds and virtual desktop hosts.

For working on a local development environment, I basically only need the two localhost sites, to communicate with each other without involving the network around my computer.

Alternatives to fixing the event messaging channel could be to:

  1. Disable Commerce caching.
  2. Restart both websites after changing cached data.

A simple configuration solves the problem

A quick solution is to enable the built-in WCF event provider, and change its configuration to use UDP-based unicast (point-to-point) messaging. This is a mix of the two methods, based on UDP and TCP, that are described individually on Episerver World.

The default configuration is UDP-based multicast messaging, which sends the event packets out on the local network and relies on the router to distribute them to subscribing sites. This is often blocked by firewalls, because they restrict either multicast or UDP.

But in my solution, I configured the two sites to:

  1. Use WCF with UDP unicast bindings.
  2. Listen on the localhost address, but on different ports.
  3. Use the address and port of the other site as a client endpoint.

This way the event packets are never sent out on the local network (because it uses the loopback interface), so no external router or firewall interferes with the transmission. It therefore also works on various networks, at work, home, an airport lounge or with no connection at all.

Because these configurations work for all team members, they can be committed to a source code repository. But you should probably use configuration transform files to replace these configurations when deploying to servers in other environments.

Configuration listings

Here are the necessary additions to the web.config file in the website project (mix them into the existing file yourself):

<configuration>
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
<extensions>
<bindingElementExtensions>
<add name="udpTransportCustom"
type="Microsoft.ServiceModel.Samples.UdpTransportElement, EPiServer.Framework.AspNet"/>
</bindingElementExtensions>
</extensions>
<services>
<service name="EPiServer.Events.Remote.EventReplication">
<endpoint name="RemoteEventServiceEndPoint"
address="soap.udp://127.0.0.1:5000/RemoteEventService"
binding="customBinding"
bindingConfiguration="RemoteEventsBinding"
contract="EPiServer.Events.ServiceModel.IEventReplication" />
</service>
</services>
<client>
<endpoint name="CommerceManager"
address="soap.udp://127.0.0.1:5001/RemoteEventService"
binding="customBinding"
bindingConfiguration="RemoteEventsBinding"
contract="EPiServer.Events.ServiceModel.IEventReplication" />
</client>
<behaviors>
<serviceBehaviors>
<behavior name="DebugServiceBehaviour">
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<customBinding>
<binding name="RemoteEventsBinding">
<binaryMessageEncoding />
<udpTransportCustom/>
</binding>
</customBinding>
</bindings>
</system.serviceModel>
</configuration>

And these are the additions to the EpiserverFramework.config file in the same website project:

<episerver.framework>
<event defaultProvider="wcfevents">
<providers>
<add name="wcfevents" type="EPiServer.Events.Providers.WcfEventProvider,EPiServer.Framework.AspNet" />
</providers>
</event>
</episerver.framework>

These are the additions to the Web.config file in the Commerce Manager project:

<configuration>
<episerver.framework>
<event defaultProvider="wcfevents">
<providers>
<add name="wcfevents" type="EPiServer.Events.Providers.WcfEventProvider,EPiServer.Framework.AspNet" />
</providers>
</event>
</episerver.framework>
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
<extensions>
<bindingElementExtensions>
<add name="udpTransportCustom"
type="Microsoft.ServiceModel.Samples.UdpTransportElement, EPiServer.Framework.AspNet"/>
</bindingElementExtensions>
</extensions>
<services>
<service name="EPiServer.Events.Remote.EventReplication">
<endpoint name="RemoteEventServiceEndPoint"
address="soap.udp://127.0.0.1:5001/RemoteEventService"
binding="customBinding"
bindingConfiguration="RemoteEventsBinding"
contract="EPiServer.Events.ServiceModel.IEventReplication" />
</service>
</services>
<client>
<endpoint name="Website"
address="soap.udp://127.0.0.1:5000/RemoteEventService"
binding="customBinding"
bindingConfiguration="RemoteEventsBinding"
contract="EPiServer.Events.ServiceModel.IEventReplication" />
</client>
<behaviors>
<serviceBehaviors>
<behavior name="DebugServiceBehaviour">
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<customBinding>
<binding name="RemoteEventsBinding">
<binaryMessageEncoding />
<udpTransportCustom/>
</binding>
</customBinding>
</bindings>
</system.serviceModel>
</configuration>

Using local Named Pipes

The above UDP-based solution can even be slightly changed to use Windows Named Pipes instead of UDP unicast. This will keep the messaging connections completely out of software firewall's reach, but it does not bring much performance gain.

These are the additions to the Web.config file in the Website project. Mix them in with the above changes. For the Web.config file in the Commerce Manager project, also switch the endpoint name and address attributes.

<system.serviceModel>
<services>
<service name="EPiServer.Events.Remote.EventReplication">
<endpoint name="RemoteEventServiceEndPoint"
address="net.pipe://127.0.0.1/WebsiteEvents"
binding="netNamedPipeBinding"
bindingConfiguration="NamedPipeBinding"
contract="EPiServer.Events.ServiceModel.IEventReplication" />
</service>
</services>
<client>
<endpoint name="CommerceManager"
address="net.pipe://127.0.0.1/CommerceManagerEvents"
binding="netNamedPipeBinding"
bindingConfiguration="NamedPipeBinding"
contract="EPiServer.Events.ServiceModel.IEventReplication" />
</client>
<bindings>
<netNamedPipeBinding>
<binding name="NamedPipeBinding"/>
</netNamedPipeBinding>
</bindings>
</system.serviceModel>