LibrarySites.Banner

SignalR and Sitecore

With the introduction of HTML5 websockets, web applications can maintain a connection to the webserver without the legacy workarounds.  Using SignalR, web developers can support legacy browsers as well as modern browser because SignalR automatically falls back to legacy implementations (e.g. forever long frame and AJAX long pulling) when needed.

In this blog, I’ll show a how you can configure Sitecore to send notifications to client after the aggregation process is triggered in Sitecore.

To use SignalR, first make sure you enable websockets for your IIS version (8+).  Next, update Sitecore web.config to following:
<httpRuntime ... requestValidationMode="2.0" targetFramework="4.5"/>   
 
<setting name="IgnoreUrlPrefixes" value="…|/signalR/" />
 
<appSettings>
<add key="ValidationSettings:UnobtrusiveValidationMode" value="None" />  
</appsettings>

Because SignalR requires .NET 4.5, we updated httpRuntime targetFramework to 4.5 in Sitecore’s web.config file.  Also, as documented in Sitecore KB, we added the attribute requestValidationMode="2.0"; other; otherwise, the following error is encountered when we open the rich text editor within the experience editor:

HttpRequestValidationException (0x80004005): A potentially dangerous Request.Form value was detected from the client

requestValidationMode Error


Also, the UnobtrusiveValidationMode setting was added to avoid the following exception:
WebForms UnobtrusiveValidationMode requires a ScriptResourceMapping for 'jquery'. Please add a ScriptResourceMapping named jquery(case-sensitive).

UnobtrusiveValidationMode error

Lastly, the IgnoreUrlPrefixes setting was updated so that SignalR requests are not captured by Sitecore.  Alternatively, we can mimic Sitecore.Pipelines.HttpRequest.IgnoreList.Processor within Sitecore.Kernel, and then patch it into httpRequestBegin like the following:

<sitecore>            <configuration xmlns:patch="https://www.sitecore.com/xmlconfig/" >  
  
<pipelines>    
<httpRequestBegin>      
<!-- Allow SignalR requests -->        
<processor type="MyNamespace.Pipelines.MyInit, MyAssembly"        
patch:before="processor[@type='Sitecore.Pipelines.HttpRequest.IgnoreList, Sitecore.Kernel']" />                   
</httpRequestBegin>      
</pipelines>    
</sitecore>  
</configuration>

namespace MyNamespace.Pipelines
{
public class MyInit: HttpRequestProcessor    
{    
public override void Process(HttpRequestArgs args)        
{        
Assert.ArgumentNotNull(args, "args");            
string[] prefixes = { "/signalr/" };            
                                //prefixes = Sitecore.Configuration.Settings.IgnoreUrlPrefixes;
  
if (prefixes.Length > 0)            
{            
string filePath = args.Url.FilePath;                
for (int i = 0; i < prefixes.Length; i++)                
{                
if (filePath.StartsWith(prefixes[i], StringComparison.OrdinalIgnoreCase))                    
{                    
args.AbortPipeline();                        
return;                        
}                    
}                
}            
  
}        
  
}    
}


Now, we need to specify the SignalR startup class. SignalR initialization, based on Katana specification, requires us to specify the startup class that implements the Open Web Interface for .NET (OWIN).  This is accomplished by providing a class that is annotated by OwinStartup attribute and provides a Configuration() method.  After creating the class we need to patch it into Sitecore initialization pipeline.  One place to patch it would be along with your custom ASP.MVC controllers.  Below example shows a possible implementation and patching location:

<configuration xmlns:patch="https://www.sitecore.com/xmlconfig/">
<sitecore>  
<pipelines>    
 
<initialize>      
<processor type="MyNamespace.Pipelines.RegisterSignalrProcessor, MyAssembly"        
patch:before="processor[@type='Sitecore.Mvc.Pipelines.Loader.InitializeRoutes, Sitecore.Mvc']" />               
</initialize>      
 
</pipelines>    
</sitecore>  
</configuration>

[assembly: OwinStartup(typeof(MyNamespace.Pipelines.RegisterSignalrProcessor))]
namespace MyNamespace.Pipelines
public class RegisterSignalrProcessor    
{    
public void Configuration(IAppBuilder app)        
{        
app.MapSignalR();            
}        
 
public virtual void Process(PipelineArgs args)        
{        
// cusotm mvc routing (if any)            
RouteTable.Routes.MapRoute("myRouteName", "myUrl",new { controller = "myMvc", action = "myMethod" });                                    
}        
}    
}

Now that Sitecore is configured, we are ready to make use of SignalR.  For this exercise I opted to notify clients when specific events occur in Sitecore.  One place that this occurs is in the end of the aggregation process, which compiles data from MongoDb into the reporting database(s).  As required by SignalR, my processor class inherits from Hub class; however, because the aggregation process is triggered from the server, I first get a handle to SignalR context by calling GetHubContext before calling client method; as shown below:


<configuration xmlns:patch="https://www.sitecore.com/xmlconfig/">
<sitecore>  
<pipelines>    
<group groupName="analytics.aggregation">    
<pipelines>        
<interactions>          
<processor type="AggregatorNotification.Pipelines.NotifierProcessor,  AggregatorNotification"            
patch:after="processor[@type='Sitecore.ExperienceAnalytics.Aggregation.Pipeline.SegmentProcessor, Sitecore.ExperienceAnalytics']" />          
</interactions>          
</pipelines>         
</group>      
</pipelines>     
</sitecore>   
</configuration>

using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
  
namespace AggregatorNotification.Pipelines
{
[HubName("notifierProcessor")]    
public class NotifierProcessor : Hub    
{    
public void Process(Sitecore.Analytics.Aggregation.Pipeline.AggregationPipelineArgs args)         
{        

// Filter data based on your needs            
string jsonData = Newtonsoft.Json.JsonConvert.SerializeObject(args.Context);            
             

//.ShowVisitorInfo() is implemented on the client side            

       var hubContext = GlobalHost.ConnectionManager.GetHubContext<NotifierProcessor>();            

hubContext.Clients.All.ShowVisitorInfo(jsonData);            
             
}        
  
// Test method to be called by SignalR client       
public void ClientBroadcastTime ()       
{        
//security: Sitecore.Context.User.IsAuthenticated            
 
Clients.All.ShowVisitorInfo(System.DateTime.Now.ToLongTimeString());            
}        
}    
}

Not that there is no ShowVisitorInfo() method above; that is because this method is implemented on the client JavaScript (below). 
The test method ClientBroadcastTime() is used to test SignalR connection.  To keep things simple, we’ll follow the SignalR tutorial and create index.html that simply displays the message sent from the aggregator:


<!DOCTYPE html>
<body>
  <a id="broadcastTime" href="#">broadcast time</a>
  
  <ul id="aggregationMessage"></ul>
  
  <!-— Required for SingalR -->
  <script src="/Scripts/jquery-1.6.4.min.js"></script>
  <script src="/Scripts/jquery.signalR-2.2.0.js"></script>
  <script src="/signalr/hubs"></script>
  
  <!--Add script to update the page and send messages.-->
  <script type="text/javascript">
  $(function () {
     
    var aggregator = $.connection.notifierProcessor;    
  
    aggregator.client.ShowVisitorInfo = function (message) {    
    var encodedMsg = $('<div />').text(message).html();        
    $('#aggregationMessage').append('<li><strong>'        
    + '</strong>:  ' + encodedMsg + '</li>');            

    };    

   // Start the connection.    
   $.connection.hub.start().done(function () {    
  
    $('#broadcastTime').click(function () { aggregator.server.clientBroadcastTime(); });        
       
   });    
  });

  </script>

</body>

</html>

Incidentally, during my testing I noticed that IE had some trouble on my local machine, because IE reverted to IE7 document model.  This can be confirm by hitting F12 and then clicking on the Document Mode icon. 


If you are not on the Edge document model, select Tools->Compatibility View Settings, and then unchecked "Display intranet sites in Compatibility View".


Last step in this exercise, is to trigger the aggregation process to view the activity.  First we browse the Sitecore site and terminate our session (i.e. Session.Abandon()) so that the visitor session is flushed into MongoDB.  Then we need to wait for the aggregation agent to run, which is set to run every 15 seconds (default interval: 0.00:00:15 - day.hh:mm:ss).  After the aggregation process runs, you will see serialized data in your browser.