Push Service With ASP - NET SignalR
Push Service With ASP - NET SignalR
NET
SignalR
Case ModulErp
Autumn 2017
ABSTRACT
The goal of this thesis was to design and implement a prototype of a push
service to the ModulERP enterprise resource planning solution. The push
service would be used in the browser interface of the system. A push
service enables real-time data transfer from a server to a client without the
client having to specifically request the data.
The result was a prototype of a push service that could be used to transmit
real-time data into the browser interface of the ModulERP system. Popups
based on the Telerik user interface component library, a status bar, and a
message drop-down menu were added to the browser interface. The
prototype has been used in informing users about the zipping of possibly
large amounts of files and in informing key users about large attachment
files in the system.
Syksy 2017
TIIVISTELMÄ
1 INTRODUCTION 1
3 SIGNALR 4
3.1 SignalR Basics 4
3.1.1 Transports 4
3.2 Architecture of a System Using SignalR 5
3.2.1 Communication Models 6
3.3 The SignalR Hub Class 7
3.3.1 The OWIN Startup Class 9
3.3.2 Methods That Clients Can Call 12
3.3.3 Calling Client Methods From the Hub 15
3.3.4 Managing Group Membership 17
3.3.5 Connection Lifetime Events 18
3.3.6 The Context Property and the State Object 18
3.3.7 Error Handling 20
3.3.8 Customizing the Hubs Pipeline 22
3.4 The JavaScript Client 23
3.4.1 The Proxy Generated by SignalR 23
3.4.2 Establishing a connection 25
3.4.3 Connection Lifetime Events on the Client 26
3.4.4 Configuring the connection 27
3.4.5 Error Handling and Client-Side Logging 29
3.5 The .NET Client 31
3.5.1 Basics 31
3.5.2 Configuring the Connection 32
3.5.3 Methods That the Server Can Call 33
3.5.4 Invoking Methods on the Server 37
3.5.5 Error Handling and Client-Side Logging 38
5 CONCLUSION 48
SOURCES 49
APPENDIXES 52
1 INTRODUCTION
The goal of this thesis was to design a push service for the ModulERP
enterprise resource planning solution maintained by JL-Soft. The library
used was ASP.NET SignalR. Reaching the goal involved two sub-goals.
The first one was researching the topic in order to find out how to
implement a push service with the library. The second one was the actual
design and creation of a prototype of the push service. The results of the
research are presented in chapter 2. The design and implementation are
described in chapter 3. The subject of the thesis was narrowed down to
the SignalR library and the implementation of the prototype with it.
2.2 Customers
Each customer’s ERP solution is separate from the others. The solution
can be delivered either as a standalone Windows application, or as a web-
based cloud service. Solutions for multiple customers can be located on
the same server.
3 SIGNALR
3.1.1 Transports
When a client method is called from the server, a packet that contains the
name and parameters of the method to be called is sent across the
transport. A method call viewed from the monitoring tool Fiddler is shown
in Figure 3. The method updateShape is called from the Hub
MoveShapeHub. (Fletcher 2014a.)
The configurations that can be altered using the OWIN startup class
include
JSONP, on the other hand, creates a <script> element that then “requests
to a remote data service location”. JSONP uses the feature of <script>
tags being able to request data in JSON format across domains. Enabling
JSONP is shown in Figure 7. The class names Startup is again
automatically used as the OWIN startup class (line 7). The pipeline is
again branched for all requests starting with “/signalr” (line 11). JSONP is
enabled in the HubConfiguration object (line 15), which is then passed as
an argument to the RunSignalR method (line 17). (Getify Solutions &
Simpson 2014.)
Methods that clients can call are declared public. A return type and
parameters can be specified, just as in any C# method. An example is
seen in Figure 8. The method is defined on line 3 – it’s referenced by
name client-side. On line 5 is an example of method functionality: a chat
message with a user name and content is added to all clients’ pages.
methods can be used when the method will be long-running or does work
that will involve waiting. When using the WebSocket transport,
asynchronous methods will not block the connection, in contrast to
synchronous methods that will.
FIGURE 8. An example of a server method that a client can call (Dykstra &
Fletcher 2014b).
14
Client methods can be called by using the Clients property of the Hub
class. Ways of selecting receiving clients are described in Table 1. An
example of calling a client method is shown in Figure 12. There, the server
application adds a chat message to all clients (line 5). The corresponding
JavaScript client method is shown in Figure 13. It is defined in the client
property of the Hub proxy (line 1, see chapter 3.4). The example method
contains simple jQuery code for adding the message to the page (lines 3-
4).
FIGURE 12. Calling a client method from the server (Dykstra & Fletcher
2014b).
16
Syntax Clients
Clients.All.clientMethod(); All clients
Clients.Caller.clientMethod(); The calling client
Clients.Others.clientMethod(); All except the calling client
Clients.Client(connectionId) A client specified by the
.clientMethod(); connection ID
Clients.AllExcept(connId1, All clients except the specified
connId2).clientMethod(); ones
Clients.Group(groupName) The clients in the specified group
.clientMethod()
Clients.User(userId).clientMethod(); A specific user
Clients.Clients(List<string> All clients in the list
connIds).clientMethod();
Clients.Groups(List<string> All groups in the list
groupIds).clientMethod();
Clients.Client(username) A user by name
.clientMethod();
Clients.Users(new string[] {”user1”, All users in the array (from SignalR
”user2”}).clientMethod(); version 2.1 on)
17
FIGURE 15. Managing groups in the Hub (Dykstra & Fletcher 2014b).
18
FIGURE 16. Managing groups on the JavaScript client (Dykstra & Fletcher
2014b).
SignalR has three Hub methods for handling connection lifetime events:
OnConnected, OnDisconnected and OnReconnected. The methods can
be overridden in the Hub class. A new connection is established every
time a browser navigates to a new page. This means that OnDisconnected
is executed first, and OnConnected after that. In some cases,
OnDisconnected does not get called at all – if a server goes down or if the
App Domain gets recycled. (Dykstra & Fletcher 2014b.)
Information about the client can be got from the Context property of the
Hub. Types of this information are described in Table 2. The connection ID
is a GUID. The same ID is used by all Hubs in the application. Query string
data can be created in the JavaScript client.
The state object is provided by the client Hub proxy, and it can be used to
pass data to the server with each method call. This is demonstrated in
Figures 17, 18 and 19. The state data is created by the client. In the Hub,
the state data is accessed from either the Clients.Caller (Figure 18) or the
Clients.CallerState (Figure 19) property. The CallerState property is used
when the Hub is strongly-typed or when the server code is written in Visual
Basic. In Figures 18 and 19, a chat message with state data is added to all
clients excluding the calling client. (Dykstra & Fletcher 2014b.)
19
FIGURE 17. Usage of the state object in the JavaScript client (Dykstra &
Fletcher 2014b).
FIGURE 18. Accessing the state object data in the Hub on the server
(Dykstra & Fletcher 2014b).
FIGURE 19. Using the CallerState property (Dykstra & Fletcher 2014b).
20
FIGURE 21. Injecting the module into the Hubs pipeline (Dykstra &
Fletcher 2014b).
FIGURE 24. A pipeline module logging method calls (Dykstra & Fletcher
2014b).
23
FIGURE 25. The logging module is registered in the startup class (Dykstra
& Fletcher 2014b).
SignalR generates a proxy that simplifies code, writes methods that the
server calls, and calls methods on the server. The usage of the proxy,
however, is optional. In Figures 26 and 27 it is demonstrated how the
client and server (Hub) can communicate both with the proxy and without
it.
Figure 26 shows how to use the generated hub proxy. The proxy is first
accessed through the SignalR connection (line 1). A client method callable
by the server-side Hub is defined on lines 2-4. In the example, it logs chat
message data to the browser console. After the SignalR connection has
been started (line 5), a Hub server method call is wired to an element on
the page (lines 6-10). In the example method, a user name and a chat
message are sent to the server. After the example method has been
called, focus is shifted to the message input field.
The proxy cannot be used, if the developer wants to register multiple event
handlers for a single client method that the server calls. Otherwise, using
the proxy can be chosen according to preference. The proxy file can be
referenced through the “/signalr/hubs” URL. (Dykstra & Fletcher 2015.)
FIGURE 26. A client using the generated proxy (Dykstra & Fletcher 2015).
FIGURE 27. A client not using the generated proxy (Dykstra & Fletcher
2015).
25
FIGURE 28. Establishing a connection with the proxy (Dykstra & Fletcher
2015)
The client can handle several connection lifetime events. These events are
described in Table 3. The events can be handled to display warning
messages etc. (Dykstra & Fletcher 2015.)
27
There are two ways to configure the connection before calling the start
method: specifying a query string to be sent to the server, and specifying
the transport method. The transport method is normally determined by the
SignalR client negotiating with the server, but this can be bypassed. The
usage of a query string is shown in Figure 30. Specifying the transport
method is demonstrated in Figure 31. (Dykstra & Fletcher 2015.)
FIGURE 31. Specifying the transport method (Dykstra & Fletcher 2015).
3.5.1 Basics
The .NET client is in many ways similar to the JavaScript client. The .NET
Hubs API can be used in, for example, Windows Store (WinRT) apps,
WPF, Silverlight, Windows Phone and console applications (Dykstra &
Fletcher 2014a).
FIGURE 34. Creating the HubConnection object and proxy and starting
the connection (Dykstra & Fletcher 2014a).
32
Methods that the server can call are defined by using the proxy’s “On()”
method in order to register an event handler. Examples are provided about
methods without parameters (Figure 36), methods with specified
parameter types (Figure 37), and methods with dynamic objects as
parameters (Figure Z). Examples include code for Windows Runtime,
Windows Presentation Foundation, Silverlight, and console clients
(Microsoft 2017b, Microsoft 2017d). Removing an event handler happens
when its Dispose method is called (Figure A). (Dykstra & Fletcher 2014a).
In Figure 36, an event handler is added in all four client types, with the
function name specified (lines 2, 9, 16 and 24). The main function of the
code examples is the output of text. In the first three event handlers, text
box content is modified (lines 4, 11, and 19). In the console event handler,
text is simply printed to the console (line 24). Each event handler is
defined as a lambda expression with no input parameters. Lambda
expressions are anonymous functions that are used to create e.g.
delegates. They also enable passing functions as method arguments, as
in the example cases. (Microsoft 2017c) On WinRT, WPF and Silverlight
platforms the code is wrapped into a platform-specific function
(“Context.Post()”, “Dispatcher.InvokeAsync()”, lines 3, 14, and 22).
In Figure 37, the code is somewhat similar to the previous example. The
parameter type (Stock) is, however, specified when all the event handlers
are created. Then, the parameter is made available as the generic lambda
expression input parameter (lines 2, 13, 21, and 29). Figure 38 is
otherwise similar to Figure 37, but the parameter types are omitted, thus
making the parameters dynamic objects. Removing the event handler by
using a stored reference to it is demonstrated in Figure 39, line 11. Storing
the reference happens on line 2.
34
Calling methods on the server happens by using the Invoke method of the
Hub proxy. If the server method has no return value, the non-generic
overload of the Invoke method must be used. On the other hand, if the
method has a return value, it must be specified as the generic type of the
Invoke method. Examples of these cases are presented in Figure 40. On
line 2, a method call with no return value is presented. On line 6, the
method is executed asynchronously with the await keyword. On lines 11
and 12, the same method is made to execute synchronously by calling
Result on the returned object.
38
FIGURE 40. Invoking server methods from the client (Dykstra & Fletcher
2014a).
If detailed errors are not enabled, the exceptions that SignalR returns
contain only minimal information about the errors. Detailed errors should of
course not be used in production, but they can be enabled for
troubleshooting and debugging. Handling errors happens by adding a
handler for the Error event of the connection object. Handling errors from
method invocations can be accomplished by wrapping the invocations in
try-catch blocks.
FIGURE 41. Error handling and logging in the .NET client (Dykstra &
Fletcher 2014a).
40
4.2 Settings
The /signalr/ URL for the implementation is defined in the XML file – for
development, it was set to “http://localhost:4275/signalr/”. There are also
IDs of timed functions defined in the XML file, and the function that
broadcasts push messages is included there in order for the push service
to be in use. These timed functions are executed at specific intervals. The
XML file also contains the download file path and URL for download-type
notifications – the files are stored in the location determined by the path
and made accessible by using the URL.
The runtime settings contain three fields that can be modified. First, there
is a checkbox that determines whether connection data is stored in the
database or just in memory. Another checkbox determines whether the
notifications are sent individually or in arrays – sending the notifications in
arrays is more efficient, as multiple notifications can be sent at once.
There is also an input field where the maximum array size can be set – if
not set, the default is maximum 50 notifications per array.
41
All message (and possibly also connection) data is stored in the JL-Soft
Oy’s Microsoft SQL Server database. There are four tables, one of which
stores message data, two that store receiver data, and one that stores
SignalR connection data. The message table is described in Figure 42.
The receiver data tables are described in Figure 43, and the structure of
the connection data table is shown in Figure 44.
The Hub class was created to actually implement SignalR. It manages all
connections, messages etc. Connection information is stored in memory in
a thread-safe ConcurrentDictionary. A ConnData class was created to
store the user name and web page ID of each connection. The dictionary
then maps SignalR connection IDs and ConnData objects together.
The Hub methods callable by the clients are described in Table 3. See
Chapter 3.6 for information about notifications and popups.
The messages are broadcasted from the ASMX web service of ModulErp.
First, if connection data is stored in the database, all of the data is fetched.
If array mode (see chapter Settings) is on, the maximum message array
size is read from settings. If the maximum size is not defined there, the
default is 50.
44
Next, a whitelist is created of users that have the push service enabled.
The hub context of the Hub class is fetched in order to access Hub
functionality.
There are several options regarding which messages are fetched from the
database (see chapter 3.6):
• info (default)
• critical
45
• download
• warning
The respective icons for all the notifications are shown in Figure 45. The
task bar and a sample notification is presented in Figure 46. The bar
contains the amount of each type of notifications, and the actual
notifications can be accessed from the drop down menu. The notifications
can be dismissed by clicking on them. Files can be downloaded by clicking
on the download button of the notification.
FIGURE 46. The task bar with the notification popup menu open.
46
Client-side methods that are callable by the server are described in Table
4. These were written in JavaScript.
4.7 Implementations
attachment exceeds the maximum file size limit. The key user can then
handle the file as needed.
In the other implementation, notifications are sent while zipping PDF files
into archives of certain maximum size. If this size is exceeded, multiple
ZIP files are created. The PDF files are end reports of work orders. The
files are generated by the ModulERP system, and users can input data
into them. After the compression is done, the ZIP archives are made
accessible to the requesting user via download type notifications.
48
5 CONCLUSION
The goal of this thesis was to create a prototype of a push service with the
SignalR library in the ModulERP solution, specifically its ASP.NET web
interface. The goal was reached – SignalR turned out to be a versatile,
easy to use library that greatly aids the creation of push services.
The push service can be further developed – a chat, for example, could be
added to the browser interface by using the service. A chat is just an
example, as the possibilities are endless. If a new version would be
developed, issues such as speed and performance could be addressed.
Multiple Hub classes could also be created in contrast to the one single
class in the prototype of the service.
49
SOURCES
Getify Solutions & Simpson, P. 2014. Defining Safer JSON-P [cited 11th
January 2016]. Available: http://json-p.org/
Microsoft. 2015. Learn About ASP.NET SignalR. The ASP.NET Site [cited
15th December 2015]. Available: http://www.asp.net/signalr/overview
Wasson, M. 2013. Getting Started with OWIN and Katana. The ASP.NET
Site [cited 18th December 2015]. Available:
http://www.asp.net/aspnet/overview/owin-and-katana/getting-started-with-
owin-and-katana
APPENDIXES