Server-side event filters

Standard .NET event model is the simplest Observer pattern implementation. Every event is dispatched to all listeners who subscribed it, so the listener is responsible for filtering out events that should not be handled. That's fine for local events, but not as good for the client-server scenarios.

Consider a chat application, for example. Chat server raises message events and sends them to all chat clients. Clients need to filter out chat messages that were sent by themselves. Server generates some extra network traffic which is simply ignored.

The situation gets worse when the server delivers private messages (i.e., messages for the certain users or groups of users). Chat server broadcasts a message as usual, and clients are responsible for the message filtering. We can easily write our evil chat client to intercept private messages.

To summarize, standard event model has the following disadvantages in a client-server scenario:
— potential security problems;
— extra network traffic for the events that are not handled.

To address these issues, the new version of Zyan adds the ability to filter event handlers at the server-side based on event arguments (and, maybe, some server call context). Built-in flexible event filter can take any predicate expression and transfer it to the server, so the event will only be delivered to the client if the expression evaluates to true. Here is an example:

// establish connection, get the proxy and subscribe to the event
using (var conn = new ZyanConnection(serverUrl, protocolSetup))
   var proxy = conn.CreateProxy<ISampleService>();
   var eventHandler = new EventHandler<SampleEventArgs>((sender, args) =>
       Console.WriteLine("Event: argument value = {0}", args.Value);

   // simple event handler: no event filter attached,
   // the event is always delivered to a client over a wire
   proxy.SampleEvent += eventHandler;

   // filtered event handler: the event is only delivered if argument equals 123
   proxy.SampleEvent += eventHandler.AddFilter((f, args) => args.Value == 123);

A note on implementation

Event filter is a serializable class that contains filtering logic. When a сlient subscribes to remote event, this object is transferred to the server along with the client's event handler delegate. The server deserializes the filter and stores it with the client event handler.

Any time the server fires an event, it executes available filters for every client event handler. The handler is only executed if the corresponding filter allows it. Therefore, if the current event arguments don't match the filter, the handler won't be executed, and no network traffic will be generated.

Session-bound events

A special case of filtered events is session-bound events: events that are only delivered to a certain client session. Session-bound events use special SessionEventArgs type with Sessions property (which holds a set of session IDs). When we need to declare a session-bound event, we derive our EventArgs type from SessionEventArgs (for example, DiagnosticsEventArgs with a string Message property). When the server fires an event, it checks for Sessions property, and if it's not empty, it only delivers an event to the session listed in Sessions set. Otherwise, an event is broadcast as usual.

Typical use case of session-bound events: tracing

Suppose we have a server component that executes SQL queries. To monitor the queries, we can declare an event which fires any type a query is executed. However, we're only interested in the queries executed by our own session. This is easily achieved using a session-bound event as shown below:

// shared code
public class SqlTraceEventArgs : SessionEventArgs
      public string SqlQuery { get; set; }

public interface IServer
     event EventHandler<SqlTraceEventArgs> SqlQueryExecuted { get; set; }

     int ExecuteNonQuery(string sql);

// server code
internal class Server : IServer
     public event EventHandler<SqlTraceEventArgs> SqlQueryExecuted { get; set; }

     public int ExecuteNonQuery(string sql)
         var handler = SqlQueryExecuted;
         if (handler != null)
             handler(null, sql);

When IServer component fires a SqlQueryExecuted event, by default it will be routed to the current session owner:

// client-side code
var proxy = connection.CreateProxy<IServer>();
proxy.SqlQueryExecuted += (s,e) => Console.WriteLine("SQL: {0}", e.SqlQuery);

// this will fire a trace event, but only the current client will receive it!
var rowsAffected = proxy.ExecuteNonQuery("DELETE FROM USERS WHERE DATE_CREATED < SYSDATE - 365");

Routing events to specific sessions

Suppose we have an event that should go to several chosen sessions. Here is how to do it:

// server-side code
internal class Server : IServer
     public event EventHandler<SessionEventArgs> SessionBoundEvent;

     // session IDs that will receive events
     private Guid[] sessions = new[] { ... };

     public void ServerMethod()
          var handler = SessionBoundEvent;
          if (handler != null)
               var e = new SessionEventArgs();
               handler(null, e);

Last edited Sep 7, 2014 at 6:09 PM by yallie, version 5


No comments yet.