11. Advanced Topics

C# Client Compatibility

AMPS clients are available for many languages. Many AMPS customers write clients using a variety of languages, often both Java and C#. While Java and C# are fundamentally different languages, they share enough syntax that it can be straightforward to port code between the two, and especially from Java to C#.

To aid in conversion from Java to C# (and from C# to Java), the C# client has a number of features that make it a little easier to bring code from Java to C#:

  • getXXX()/setXXX() Java-style getters and setters are provided corresponding to properties on the Message class. For example, given a variable message of type Message, the code:

    string userName = message.UserName

    and

    string userName = message.getUserName()

    are equivalent.

  • C# Parameters that take lambda functions also take an interface type. The AMPS Java client defines interfaces such as ClientMessageHandler that your application implements, with a single invoke() method that is called when an event occurs. In C#, the AMPS client uses lambda functions and delegates to provide equivalent functionality. However, the same *Handler interfaces exist in C#, and instead of passing a lambda function, you may also implement these interfaces and pass in derived classes. While doing so would be inconvenient in C#, providing this symmetry allows your Java and C# to be ported interchangeably.

  • Java-style method name conventions are used throughout AMPS. In .NET, method names often begin with a capitalized first letter (e.g. Connect() instead of connect()). However, the C# AMPS client retains the capitalization style of the Java client where possible, making porting straightforward.

Strong Naming

Starting with the 5.0 release of the C# client, the included Release build of the C# client is strong-named.

The build files included with the client do not produce a strong-named assembly. To prevent misidentification of assemblies, 60East does not ship the key used to strong-name the assembly, and has removed references to the key from the build files included with the client. What this means is that, if you build your own version of the assembly, you must provide your own strong name key and update the build process to reference that key.

For more information on strong naming assemblies, see the MSDN article at https://msdn.microsoft.com/en-us/library/wd40t7ad(v=vs.110).aspx>.

SSL Certificates and the C# Client

The AMPS C# client uses the standard .NET mechanisms for creating a SSL connection. This means that you manage certificates stores and trust chains for the AMPS C# client as you would for any other .NET application.

For information on creating SSL certificates for testing, see the MSDN article at https://msdn.microsoft.com/en-us/library/ms733813(v=vs.110).aspx.

Transport Filtering

The AMPS C# client offers the ability to filter incoming and outgoing messages in the format they are sent and received on the network. This allows you to inspect or modify outgoing messages before they are sent to the network, and incoming messages as they arrive from the network. To create a transport filter, you implement the interface TransportFilter, construct an instance of the filter class, and install the filter with the setTransportFilter method on the transport.

The AMPS C# client does not validate any changes made by the transport filter. This interface is most useful for application debugging or transport development.

The client includes a sample filter, TransportTraceFilter, that simply writes incoming and outgoing buffers to a TextWriter.

Notice that the transport filter function is called with the verbatim contents of data received from AMPS. This means that, for incoming data, the function may not be called precisely on message boundaries, and that the binary length encoding used by the client and server will be presented to the transport filter.

Working with Messages & Byte Buffers

The AMPS C# client allows you to publish messages that contain data from byte buffers. When working with byte buffers in AMPS, it’s best to follow these simple conventions.

AMPS provides overloaded publish() methods that allow you to publish messages from various formats. For example, to publish a message with data from a byte buffer, you must first provide the data as a byte[], the position of the data, and the length of the data. Also, the message topic, to which the message will be published, must also be provided as a byte[] along with its position and length.

The example below shows how to serialize an object into a byte buffer, then publish the message to AMPS using the publish() method.

...

// create the topic string, and decode it to a byte[]
var topic = "messages";
byte[] topicBytes = System.Text.Encoding.UTF8.GetBytes(topic.ToCharArray());

// create the payload and construct the composite
CompositeMessageBuilder builder = new CompositeMessageBuilder();
builder.append(data.getBytes(), 0, data.getBytes().Length);

// set the topic to messages, and create a field for the builder
// to extract the buffer
string topic = "messages";

// construct the payload object
Employee emp;

// create the BinaryFormatter used to serialize the object
BinaryFormatter bf = new BinaryFormatter();

using (var memStream = new MemoryStream())
{

    // serialize the object to a MemoryStream
    bf.Serialize(memStream, emp);

    // set the byte of the message to a field
    field.set(ms.GetBuffer(), 0, ms.GetBuffer().Length);
}

// publish to the "messages" topic using byte buffers
client.publish(topicBytes, 0, topicBytes.Length, myField.buffer, myField.position, myField.length);

...

AMPS also provides a way to access the raw bytes of a message when subscribing to those messages. The method getDataRaw() returns a Field that is composed of a byte buffer, position of the data in the buffer, and length. This data could then be deserialized and converted to an object for further use.

The example below shows how to access the raw bytes of a message, and then shows how to deserialize the bytes of that message to a arbitrary C# Object.

...

// store the message raw data to a variable
var rawBytes = message.getDataRaw();

// create the container object for the data
Employee emp;

using (var memStream = new MemoryStream())
{
    // construct the BinaryFormatter that will parse
    // the MemoryStream data to an Object
    var formatter = new BinaryFormatter();

    // write, to the MemoryStream, the message raw data
    memStream.Write(rawBytes.buffer, rawBytes.position, rawBytes.length);
    memStream.Seek(0, SeekOrigin.Begin);

    // deserialize the MemoryStream back to the original object
    emp = bf.Deserialize(memStream);
}

...