{"id":2645,"date":"2018-11-13T20:47:40","date_gmt":"2018-11-13T19:47:40","guid":{"rendered":"https:\/\/www.itidea.nl\/?p=2645"},"modified":"2018-11-13T16:47:04","modified_gmt":"2018-11-13T15:47:04","slug":"notification-azure-signalr-sentiment-of-comments-given-on-a-sharepoint-page-part-3","status":"publish","type":"post","link":"https:\/\/www.itidea.nl\/index.php\/notification-azure-signalr-sentiment-of-comments-given-on-a-sharepoint-page-part-3\/","title":{"rendered":"Notification &#8211; Azure SignalR &#8211; Sentiment of comments given on a SharePoint page &#8211; Part 3"},"content":{"rendered":"<p><a href=\"https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/11\/5-signalR-overview.png\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-2664\" src=\"https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/11\/5-signalR-overview.png\" alt=\"\" width=\"867\" height=\"895\" srcset=\"https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/11\/5-signalR-overview.png 867w, https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/11\/5-signalR-overview-291x300.png 291w, https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/11\/5-signalR-overview-768x793.png 768w\" sizes=\"auto, (max-width: 867px) 100vw, 867px\" \/><\/a><\/p>\n<p>The previous two posts (<a href=\"https:\/\/www.itidea.nl\/index.php\/sentiment-of-comments-given-on-a-sharepoint-page\/\">part 1<\/a>, <a href=\"https:\/\/www.itidea.nl\/index.php\/webhook-sentiment-of-comments-given-on-a-sharepoint-page-part-2\/\">part 2<\/a>) explained the analysis of the sentiment of a comment given on a page and a webhook that was triggered when this sentiment score was written to the list. The last posts ended showing the value of the RawSentiment field of the item that was changed or added.<\/p>\n<p>This post will show a notification to specific persons in eg a Marketing group when the sentiment score is &#8216;extreme&#8217;. When a very low or high score is returned from the Cognitive Service there is probably something going on about the messages on that specific page that maybe requires clarification (very low score) or something else.<\/p>\n<p>To be notified on &#8216;extreme&#8217; scores Azure SignalR Service is used in combination with Azure Functions 2.0 (<a href=\"https:\/\/azure.microsoft.com\/en-us\/blog\/introducing-azure-functions-2-0\/\">https:\/\/azure.microsoft.com\/en-us\/blog\/introducing-azure-functions-2-0\/<\/a>). Be aware that Azure Functions 2.0 runtime uses .NET Core 2.0, while version 1.x used the .NET framework 4.x.<\/p>\n<h3>Create a new Azure SignalR Service instance<\/h3>\n<p>After logging in to the <a href=\"https:\/\/portal.azure.com\/\">Azure Portal<\/a>\u00a0create a new SignalR Service instance.<br \/>\n<a href=\"https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/11\/1-signalR-service.png\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-medium wp-image-2653\" src=\"https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/11\/1-signalR-service-191x300.png\" alt=\"\" width=\"191\" height=\"300\" srcset=\"https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/11\/1-signalR-service-191x300.png 191w, https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/11\/1-signalR-service.png 325w\" sizes=\"auto, (max-width: 191px) 100vw, 191px\" \/><\/a><\/p>\n<p>After the instance is created get the connection string from the Keys tab. The connection string is going to be used in the Azure Function which will be created next.<br \/>\n<a href=\"https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/11\/2-signalR-service-keys.png\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-2652 size-large\" src=\"https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/11\/2-signalR-service-keys-1024x565.png\" alt=\"\" width=\"600\" height=\"331\" srcset=\"https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/11\/2-signalR-service-keys-1024x565.png 1024w, https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/11\/2-signalR-service-keys-300x165.png 300w, https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/11\/2-signalR-service-keys-768x424.png 768w, https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/11\/2-signalR-service-keys-940x518.png 940w, https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/11\/2-signalR-service-keys.png 1155w\" sizes=\"auto, (max-width: 600px) 100vw, 600px\" \/><\/a><\/p>\n<h3>SignalR input binding<\/h3>\n<p>A client must retrieve the service endpoint URL to be able to connect to Azure SignalR Service created in the previous step.<br \/>\nTherefor a new HttpTriggered function is created where the AzureSignalRConnectionInfo object is injected using the SignalRConnectionInfo attribute. In the attribute the name of the hub (choose your own) needs to be specified. SignalR uses hubs to communicate between clients and servers.<br \/>\nThe connection string of the Azure SignalR Service instance has to be stored in local.settings.json in the default settings name AzureSignalRConnectionString.<\/p>\n<blockquote><p>Azure SignalR Service is generally available. However, SignalR Service bindings for Azure Functions are currently in preview.<\/p><\/blockquote>\n<p>To get the required Azure Function bindings for the SignalR Service add a reference to the<br \/>\nMicrosoft.Azure.WebJobs.Extensions.SignalRService package, version 1.0.0-preview1-10002. This is the only version available at the time of writing this post.<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n\r\n&#x5B;FunctionName(&quot;negotiate&quot;)]\r\npublic static IActionResult GetSignalRInfo(\r\n  &#x5B;HttpTrigger(AuthorizationLevel.Anonymous, &quot;post&quot;, &quot;options&quot;)]HttpRequest req,\r\n  &#x5B;SignalRConnectionInfo(HubName = &quot;broadcast&quot;)]SignalRConnectionInfo info)\r\n{\r\n  \/\/ Azure function doesn't support CORS well, workaround it by explicitly return CORS headers\r\n  req.HttpContext.Response.Headers.Add(&quot;Access-Control-Allow-Credentials&quot;, &quot;true&quot;);\r\n  if (req.Headers&#x5B;&quot;Origin&quot;].Count &gt; 0) req.HttpContext.Response.Headers.Add(&quot;Access-Control-Allow-Origin&quot;, req.Headers&#x5B;&quot;Origin&quot;]&#x5B;0]);\r\n  if (req.Headers&#x5B;&quot;Access-Control-Request-Headers&quot;].Count &gt; 0) req.HttpContext.Response.Headers.Add(&quot;Access-Control-Allow-Headers&quot;, req.Headers&#x5B;&quot;access-control-request-headers&quot;]&#x5B;0]);\r\n\r\n  return info != null\r\n    ? (ActionResult)new OkObjectResult(info)\r\n    : new NotFoundObjectResult(&quot;Failed to get SignalR connection information&quot;);\r\n}\r\n<\/pre>\n<h3>ProcessMessage function<\/h3>\n<p>In <a href=\"https:\/\/www.itidea.nl\/index.php\/webhook-sentiment-of-comments-given-on-a-sharepoint-page-part-2\/\">part 2<\/a> of this series a function named ProcessMessage was created to be notified of all changes that happened on the list that&#8217;s been watched by subscribing to a webhook endpoint.<br \/>\nThis function is going to put a message on a Service Bus Queue when an &#8216;extreme&#8217; sentiment value is registered, say below 0.2 or above 0.8.<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n...\r\n\r\n\/\/ put message on Service Bus Queue to trigger SignalR service\r\nvar content = $&quot;Extreme score detected: {rawSentiment}&quot;;\r\nvar logMessage = string.Empty;\r\nvar connectionString = ConfigurationManager.AppSettings&#x5B;&quot;ServiceBusConnection&quot;];\r\nvar queueName = ConfigurationManager.AppSettings&#x5B;&quot;QueueNameBroadcast&quot;];\r\n\r\nif (string.IsNullOrEmpty(connectionString) || string.IsNullOrEmpty(queueName))\r\n{\r\nlogMessage = &quot;AppSettings not found.&quot;;\r\nlog.Error(logMessage);\r\nthrow new ArgumentNullException(logMessage);\r\n}\r\n\r\nvar client = QueueClient.CreateFromConnectionString(connectionString, queueName);\r\nBrokeredMessage msg = new BrokeredMessage(content);\r\nawait client.SendAsync(msg);\r\n\r\nlog.Info(&quot;Message added to queue&quot;);\r\n\r\n...\r\n<\/pre>\n<h3>SignalR output binding<\/h3>\n<p>To send messages to all connected clients using Azure SignalR Service the output binding is used. To do so a new ServiceBusTriggered function is created which listens to the queue where the ProcessMessage function is putting messages to.<br \/>\nAn IAsyncCollector object is injected in the function using the SignalR attribute. In the attribute the name of the hub needs to be specified again.<br \/>\nTo actually broadcast a message the AddAsync method needs to be called on IAsyncCollector signalRMessages with a new SignalRMessage as parameter.<br \/>\nThe Target is the name of the method to be invoked on each client. The arguments property is an array of zero or more objects to be passed to the client method.<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n&#x5B;FunctionName(&quot;BroadcastMessages&quot;)]\r\npublic static void Run(\r\n&#x5B;ServiceBusTrigger(&quot;%QueueName%&quot;, Connection = &quot;ServiceBusConnection&quot;)]string\u00a0 \u00a0 \u00a0myQueueItem,\r\n&#x5B;SignalR(HubName = &quot;broadcast&quot;)]IAsyncCollector&lt;SignalRMessage&gt; signalRMessages)\r\n{\r\n\/\/actually send a new message\r\nsignalRMessages.AddAsync(new SignalRMessage()\r\n{\r\nTarget = &quot;notify&quot;,\r\nArguments = new object&#x5B;] { myQueueItem }\r\n});\r\n}\r\n<\/pre>\n<h3>Client<\/h3>\n<p>At last we need a client (or more) to subscribe to the messages broadcasted by the Azure SignalR Service. Therefor an SPFx application customizer can be used.<br \/>\nInstall the JavaScript and TypeScript clients for SignalR for ASP.NET Core using<br \/>\n<strong>npm install @aspnet\/signalr<\/strong><\/p>\n<p>To connect to a hub a HubConnectionBuilder object has to be created to configure a HubConnection instance.<br \/>\nOnce the connection is in place the client can subscribe to broadcast messages of the service by calling the &#8216;on&#8217; function of the HubConnectionBuilder with the method name &#8216;notify&#8217; as the first parameter. This is the method name specified in the Target of the AddAsync method.<br \/>\nAt last the connection has to be started.<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n\r\nthis.hubConnection = new signalR.HubConnectionBuilder()\r\n  .withUrl(hubUrl)\r\n  .configureLogging(signalR.LogLevel.Information)\r\n  .build();\r\n\r\nthis.hubConnection.on('notify', (data: any) =&gt; {\r\n  console.log(data);\r\n});\r\n\r\nthis.hubConnection.start().catch(err =&gt; console.error(err.toString()));\r\n\r\n<\/pre>\n<blockquote><p>As a best practice, call the\u00a0start\u00a0method on the\u00a0HubConnection\u00a0after\u00a0&#8216;on&#8217;. Doing so ensures your handlers are registered before any messages are received.<\/p><\/blockquote>\n<p>Once the connection has been established the following message can be seen in the console of the browser:<br \/>\n<a href=\"https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/11\/3-signalR-service-connection-established-1.png\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-2661\" src=\"https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/11\/3-signalR-service-connection-established-1.png\" alt=\"\" width=\"695\" height=\"43\" srcset=\"https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/11\/3-signalR-service-connection-established-1.png 695w, https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/11\/3-signalR-service-connection-established-1-300x19.png 300w\" sizes=\"auto, (max-width: 695px) 100vw, 695px\" \/><\/a><\/p>\n<p>And when a message is broadcasted it appears in the console.<br \/>\n<a href=\"https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/11\/4-signalR-service-console-msg.png\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-2654\" src=\"https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/11\/4-signalR-service-console-msg.png\" alt=\"\" width=\"284\" height=\"63\" \/><\/a><\/p>\n<p>At this moment the broadcasted message is written to the console, but this can be improved a lot of course!<br \/>\nThis could be shown as an informative notification at the top of the page for example using the placeholders of the page.<\/p>\n<h3>Summary<\/h3>\n<p>There was a lot to do in this series of posts.<br \/>\n<a href=\"https:\/\/www.itidea.nl\/index.php\/sentiment-of-comments-given-on-a-sharepoint-page\/\">Part 1<\/a> collected comments of a page and determined using Cognitive Services the sentiment of them. The results were stored in a list.<br \/>\n<a href=\"https:\/\/www.itidea.nl\/index.php\/webhook-sentiment-of-comments-given-on-a-sharepoint-page-part-2\/\">Part 2<\/a> added a webhook to this list to be able to be notified of changes to this list. These changes were put on a ServiceBus Queue. Another function listened to this queue and processed the message to be able to determine (&#8216;extreme&#8217;) sentiment scores.<br \/>\nPart 3, this post, created an Azure SignalR Service en Azure Functions to be able to get connection info and to broadcast messages to clients, it this case &#8216;extreme&#8217; sentiment scores collected. At last clients were notified of the &#8216;extreme&#8217; scores.<\/p>\n<p><a href=\"https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/11\/5-signalR-overview.png\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-2664\" src=\"https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/11\/5-signalR-overview.png\" alt=\"\" width=\"867\" height=\"895\" srcset=\"https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/11\/5-signalR-overview.png 867w, https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/11\/5-signalR-overview-291x300.png 291w, https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/11\/5-signalR-overview-768x793.png 768w\" sizes=\"auto, (max-width: 867px) 100vw, 867px\" \/><\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>The previous two posts (part 1, part 2) explained the analysis of the sentiment of a comment given on a page and a webhook that was triggered when this sentiment score was written to the list. The last posts ended &#8230; <a class=\"more-link\" href=\"https:\/\/www.itidea.nl\/index.php\/notification-azure-signalr-sentiment-of-comments-given-on-a-sharepoint-page-part-3\/\">Read More &raquo;<\/a><\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[49,39,41],"tags":[50,20,37,46,48],"class_list":["post-2645","post","type-post","status-publish","format-standard","hentry","category-azure","category-office-365","category-sharepoint","tag-azure","tag-c","tag-office365","tag-sharepoint","tag-spfx"],"_links":{"self":[{"href":"https:\/\/www.itidea.nl\/index.php\/wp-json\/wp\/v2\/posts\/2645","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.itidea.nl\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.itidea.nl\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.itidea.nl\/index.php\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.itidea.nl\/index.php\/wp-json\/wp\/v2\/comments?post=2645"}],"version-history":[{"count":19,"href":"https:\/\/www.itidea.nl\/index.php\/wp-json\/wp\/v2\/posts\/2645\/revisions"}],"predecessor-version":[{"id":2675,"href":"https:\/\/www.itidea.nl\/index.php\/wp-json\/wp\/v2\/posts\/2645\/revisions\/2675"}],"wp:attachment":[{"href":"https:\/\/www.itidea.nl\/index.php\/wp-json\/wp\/v2\/media?parent=2645"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.itidea.nl\/index.php\/wp-json\/wp\/v2\/categories?post=2645"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.itidea.nl\/index.php\/wp-json\/wp\/v2\/tags?post=2645"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}