HowTo: Send Notifications from SQL Server using SignalR

sr

Overview

In our current project we’ve to push some notifications directly from the database server (SQL Server 2014) to different apps. For example, we’ve scheduled some SSIS (SQL Server Integration Services) projects that pre-calculates large amounts of data. And well, should be nice notify all the apps when new data is available without unnecessary round trips to database. And you know? we’re already using SignalR in our apps… so, why not try to build a lightweight client for sending SignalR notifications?

Dear architects, please, don’t judge me :P

The idea

  1. I finally decided to create a small project in C# that connects to our SignalR (which is self-hosted in a Windows Service). That project contains a single method for sending notifications with a message (just a string for simplify).
  2. Once the project is ready and tested, the next step is create an ASSEMBLY, that is, a module that contains class metadata and managed code as an object in an instance of SQL Server. By referencing this module, common language runtime (CLR) functions, stored procedures, triggers, user-defined aggregates, and user-defined types can be created in the database.
  3. And finally, create a Stored Procedure that use our previous assembly CLR function (for isolation purposes and decoupling), and then use it in our SSIS projects with a simply ‘Execute SQL Task‘ when the process finalizes.

Creating the C# project

Create a blank class Library project and add the SignalR client (Nuget will add depending assemblies -like Newtonsoft.Json- automatically)

addSignalRClient

Once we added SignalR Client references to the project we need to create the C# method, so let’s add a class item to the project with this code:

public class SqlServerNotificationsClientService
{
    internal static HubConnection hubConnection = null;
    internal static IHubProxy hubProxy = null;
    [SqlFunction()]
    public static void SendMessageToHub(
        string severURL, string hubName, string hubMethod, params object[] args)
    {
        try
        {
            if (hubConnection == null || isConnectionClosed())
                hubConnection = new HubConnection(severURL.Trim()) 
                { Credentials = CredentialCache.DefaultNetworkCredentials };
            if (hubProxy == null || isConnectionClosed())
                hubProxy = hubConnection.CreateHubProxy(hubName.Trim());
            if (isConnectionClosed())
                hubConnection.Start().Wait();
            if(args.Length == 0)
                hubProxy.Invoke(hubMethod.Trim()).Wait();
            else
                hubProxy.Invoke(hubMethod.Trim(), args).Wait();
        }
        catch (Exception exc)
        {
            SqlContext.Pipe.Send(string.Format("{0}.{1} error: '{2}'{3}", 
                hubName.Trim(), hubMethod.Trim(), exc.Message, Environment.NewLine));
        }
    }

    private static bool isConnectionClosed()
    {
        return !(hubConnection.State == ConnectionState.Connected
            || hubConnection.State == ConnectionState.Reconnecting
            || hubConnection.State == ConnectionState.Connecting);
    }
}

Note: Before continue, take a look to the last parameter ‘args’. This parameter allows you to pass a different number of arguments to this method, which is nice because when we invoke the hub’s method we can use the same argument type. Unfortunately this argument type is not compatible with SQL CRL and will crash when creating the Stored Procedure in the database :( So we need to change this argument to a simple string.

This code is just an example, maybe would be a better idea split it into different methods (remember the ‘S’ of SOLID?) :)

Let’s assume the code is ok and is able to connect and send the notification. Now…

Create the SQL Assembly

In order to deploy your assemblies to a SQL Server you need the same version of the .NET Framework installed on the server. Yep, it’s too obvious but…

Create a folder in the server called ‘C:\SQLServerAssemblies’ (or wherever you want) and copy all the assemblies generated by the previous project.

signalRClientBaseAssemblies

But we’ve not finished yet… Now we’ve to add some related assemblies (assemblies dependencies). Otherwise, assembly creation will fail.

signalRClientFullListAssemblies

You can find these assemblies in the same server, in the correct version of .NET Framework. In my case:

C:\Windows\Microsoft.NET\Framework64\v4.0.30319 -> Microsoft Framework 4.5

Once we’ve all the necessary assemblies copied, let’s create the assembly. First we need to activate some things in the server:

ALTER DATABASE Falcon SET trustworthy ON

exec sp_configure 'show advanced options', 1;
GO
RECONFIGURE;
GO

exec sp_configure 'clr enabled', 1;
GO
RECONFIGURE;
GO

exec sp_configure 'show advanced options', 0;
GO
RECONFIGURE;
GO

CREATE ASSEMBLY Falcon_SqlProxy from 
  'C:\SQLServerAssemblies\Falcon.NotificationsClient.SqlProxy.dll' 
WITH PERMISSION_SET = UNSAFE 


signalRSqlAssemblies

Yes! Assemblies created successfully in SQL Server ;)

Creating the SP

Finally we only need to create the Stored Procedure. I recommend you use a custom schema. I’ve created one called ‘clr’

CREATE PROCEDURE clr.SendMessageToHub
@url nchar(125) ,
@hubName nchar(75),
@hubMethod NCHAR(75),
@message NVARCHAR(MAX)
AS
EXTERNAL NAME [Falcon_SqlProxy].[Falcon.NotificationsClient.SqlProxy.SqlServerNotificationsClientService].SendMessageToHub

Remember the note? Have you changed the last argument of the method  SendMessageToHub to a string?

If you did it and copied all the assemblies to the folder it should have worked like a charm ;)

Now it’s time to test our SP… Success!

signalR_SPOk

Bonus:

If you wanna use this SP from a SSIS project to send notifications to your apps, just add a Execute SQL Task to your project and configure the SQL statement:

signalRExecuteSQLTask

Hope this helps! :)

Curiosity: What happens if you remove all the Trim() in the c# code? Why connection to SignalR fails? ;)

See you,

DotNet Spain Conference 2015

Este fin de semana he tenido la oportunidad de participar en el mayor evento de la comunidad .NET en España hasta la fecha.

Se ha realizado en la Universidad de Alcalá, con record de asistentes. Cosa que no me extraña viendo la cantidad de charlas y hands on lab que se han realizado en varios tracks en paralelo. La verdad es que ha sido un éxito a todos los niveles, y sólo puedo decir que va a ser difícil superarlo en futuras ediciones. Se ha puesto el listón muy alto! :D

dotnetspain

En lo que respecta a mi, tuve la gran suerte de presentar -y que me aprobaran- una charla conjunta con mi colega Alex Casquete, sobre buenas prácticas sobre async, algo que precisamente no es mainstream pero que curiosamente ha tenido una aceptación muy buena. De hecho ha sido mejor de la que yo pensaba que podía tener en un principio, así que muchas gracias a todos por tanto feedback positivo!

Os dejo aquí las slides de la charla y las demos que utilizamos, ya que -para mi sorpresa- me las ha pedido bastante gente:

Sin embargo, en un evento como este no todo son sesiones técnicas. Lo mejor de todo es que he tenido la oportunidad de volver a encontrarme con un montón de viejos conocidos, con los que compartir buenos momentos y algunas cervezas, de modo que el networking ha sido… fantástico!

Os dejo con algunas fotos del eventazo. Gracias a todos! :D