ably / ably-dotnet

.NET, MAUI, Xamarin, Mono and Unity client library SDK for Ably realtime messaging service
https://ably.com/download
Apache License 2.0
45 stars 22 forks source link
c-sharp client-library csharp dotnet maui maui-app mono realtime realtime-messaging rest sdk unity uwp xamarin

ably-dotnet

NuGet version Windows - build and test MacOS - build and test Linux - build and test Features

Ably is the platform that powers synchronized digital experiences in realtime. Whether attending an event in a virtual venue, receiving realtime financial information, or monitoring live car performance data – consumers simply expect realtime digital experiences as standard. Ably provides a suite of APIs to build, extend, and deliver powerful digital experiences in realtime for more than 250 million devices across 80 countries each month. Organizations like Bloomberg, HubSpot, Verizon, and Hopin depend on Ably’s platform to offload the growing complexity of business-critical realtime data synchronization at global scale. For more information, see the Ably documentation.

This is a .NET client library for Ably which targets the 2.0 client library specification. You can see the features this client supports in our feature support matrix.

Supported platforms

Documentation

Visit https://ably.com/docs for a complete API reference and more examples.

Installation

The client library is available as a nuget package.

You can install it from the Package Manager Console:

PM> Install-Package ably.io

or using the .NET CLI in your project directory:

dotnet add package ably.io

Using the Realtime API

The Realtime library is typically used client-side in your applications. It maintains a persistent connection to Ably and is a stateful library. Find out when you should use the REST or Realtime library.

Instancing a Realtime client

Creating a Realtime client:

// Using basic auth with API key
// Note in production, an API key should not be used in untrusted mobile/browser clients
var realtime = new AblyRealtime("<api key>");
// Using token auth with token string
// Note this token is not renewable - a token callback should be used in production
var realtime = new AblyRealtime(new ClientOptions { Token = "token" });

If you do not have an API key, sign up for a free API key now

Connection

Connecting and observing connection state changes. By default the library automatically initializes a connection.

realtime.Connection.On(ConnectionEvent.Connected, args =>
{
    // Do stuff  
});

To disable the default automatic connect behavior of the library, set AutoConnect = false when initializing the client.

var realtime = new AblyRealtime(new ClientOptions("<api key>") { AutoConnect = false });
// Some code
realtime.Connect();

Subscribing to connection state changes and observing errors:

realtime.Connection.On(args =>
{
    var currentState = args.Current; // Current state the connection transitioned to
    var previousState = args.Previous; // Previous state
    var error = args.Reason; // If the connection error-ed the Reason object will be populated.
});

Subscribing to a channel

Channels are the medium through which messages are distributed.

To create a channel object:

IRealtimeChannel channel = realtime.Channels.Get("test");

Subscribe to all events published on that channel:

channel.Subscribe(message =>
{
    var name = message.Name;
    var data = message.Data;
});

Subscribing to specific events:

channel.Subscribe("myEvent", message =>
{
    var name = message.Name;
    var data = message.Data;
});

Observing channel state changes and errors:

channel.On(args =>
{
    var state = args.NewState; // Current channel State
    var error = args.Error; // If the channel error-ed it will be reflected here
});

or

channel.On(ChannelState.Attached, args =>
{
    // Do stuff when channel is attached
});

Publishing to a channel

The client support a callback and async publishing. The simplest way to publish is:

channel.Publish("greeting", "Hello World!");

with a callback:

channel.Publish("greeting", "Hello World!", (success, error) =>
{
    // If publish succeeded 'success' is 'true'
    // if publish failed 'success' is 'false' and 'error' will contain the specific error
});

and the async version which if you await it will complete when the message has been acknowledged or rejected by the Ably service:

var result = await channel.PublishAsync("greeting", "Hello World!");
// You can check if the message failed
if (result.IsFailure)
{
    var error = result.Error; // The error reason can be accessed as well
}
var serializedData = JsonConvert.SerializeObject(message,
    new JsonSerializerSettings
    {
        ContractResolver = new CamelCasePropertyNamesContractResolver()
    });

var ablyMessage = new Message("name", serializedData) {Encoding = "json"};

channel.Publish(ablyMessage);

Getting channel history

Calling history returns a paginated list of message. The object is of type PaginatedResult<Message> and can be iterated through as a normal list.

var history = await channel.HistoryAsync();
// Loop through current history page
foreach (var message in history.Items)
{
    // Do something with message
}
// Get next page.
var nextPage = await history.NextAsync();

Getting presence history

Getting presence history is similar to how message history works. You get back PaginatedResult<PresenceMessage> and can navigate or iterate through the page

var presenceHistory = await channel.Presence.HistoryAsync();
// Loop through the presence messages
foreach (var presence in presenceHistory.Items)
{
    // Do something with the messages
}

var presenceNextPage = await presenceHistory.NextAsync();

Subscribing to a channel in delta mode

Subscribing to a channel in delta mode enables delta compression. This is a way for a client to subscribe to a channel so that message payloads sent contain only the difference (ie the delta) between the present message and the previous message on the channel.

Request a Vcdiff formatted delta stream using channel options when you get the channel:

var channelParams = new ChannelParams();
channelParams.Add("delta", "vcdiff");
var channelOptions = new ChannelOptions();
channelOptions.Params = channelParams;
IRealtimeChannel channel = ably.Channels.Get(ChannelName, channelOptions);

Beyond specifying channel options, the rest is transparent and requires no further changes to your application. The message.Data instances that are delivered to your Action<Message> handler continue to contain the values that were originally published.

If you would like to inspect the Message instances in order to identify whether the Data they present was rendered from a delta message from Ably then you can see if Extras.Delta.Format equals "vcdiff".

Symmetric end-to-end encrypted payloads on a channel

When a 128-bit or 256-bit key is provided to the library, all payloads are encrypted and decrypted automatically using that key on the channel. The secret key is never transmitted to Ably and thus it is the developer's responsibility to distribute a secret key to both publishers and subscribers.

var secret = Crypto.GetRandomKey();
var encryptedChannel = realtime.Get("encrypted", new ChannelOptions(secret));
encryptedChannel.Subscribe(message =>
{
    var data = message.data; // Sensitive data (encrypted before published)
});
encryptedChannel.Publish("name (not encrypted)", "sensitive data (encrypted before published)");

Executing callbacks on Main/UI thread

options.CustomContext = SynchronizationContext.Current;

Increase Transport send and receive buffers

In .NET Framework projects, we discovered issues with the .NET implementation of the web socket protocol during times of high load with large payloads (over 50kb). This is better described in https://github.com/ably/ably-dotnet/issues/446 To work around the problem, you need to adjust websocket library's buffer to it's maximum size of 64kb. Here is an example of how to do it.

var maxBufferSize = 64 * 1024;
var options = new ClientOptions();
var websocketOptions = new MsWebSocketOptions() { SendBufferInBytes = maxBufferSize, ReceiveBufferInBytes = maxBufferSize };
options.TransportFactory = new MsWebSocketTransport.TransportFactory(websocketOptions);
var realtime = new AblyRealtime(options);

Enable logging

Define a new class that implements ILoggerSink interface.

class CustomLogHandler : ILoggerSink
{
    public void LogEvent(LogLevel level, string message)
    {
        Console.WriteLine($"Handler LogLevel : {level}, Data :{message}");
    }
}

Update clientOptions for LogLevel and LogHandler.

clientOpts.LogLevel = LogLevel.Debug;
clientOpts.LogHandler = new CustomLogHandler();

Using the REST API

The REST library is typically used server-side in your applications and is stateless. Find out when you should use the REST or Realtime library.

Instancing a REST client

Creating a REST client and channel:

var client = new AblyRest("<api key>");
IRealtimeChannel channel = client.Channels.Get("chat");

If you do not have an API key, sign up for a free API key now

Publishing a message to a channel

await channel.PublishAsync("name", "data");

If the publish is not successful an error will be thrown of type AblyException containing error codes and error description

try 
{
    await channel.PublishAsync("name", "errorData");
} 
catch(AblyException ablyError) 
{
    // Log error
}
var serializedData = JsonConvert.SerializeObject(message,
    new JsonSerializerSettings
    {
        ContractResolver = new CamelCasePropertyNamesContractResolver()
    });

var ablyMessage = new Message("name", serializedData) {Encoding = "json"};

channel.Publish(ablyMessage);

Querying channel history

var historyPage = await channel.HistoryAsync();
foreach (var message in historyPage.Items)
{
    // Do something with each message
}
// Get the next page
var nextHistoryPage = await historyPage.NextAsync();

Current presence members on a channel

var presence = await channel.Presence.GetAsync();
var first = presence.Items.FirstOrDefault();
var clientId = first.clientId; // 'clientId' of the first member present
var nextPresencePage = await presence.NextAsync();
foreach (var presenceMessage in nextPresencePage.Items)
{
    // Do stuff with next page presence messages
}

Querying the presence history

// Presence history
var presenceHistory = await channel.Presence.HistoryAsync();
foreach (var presenceMessage in presenceHistory.Items)
{
    // Do stuff with presence messages
}

var nextPage = await presenceHistory.NextAsync();
foreach (var presenceMessage in nextPage.Items)
{
    // Do stuff with next page messages
}

Authentication

var rest = new AblyRest("API_KEY");

Using the Token auth at client side

var options = new ClientOptions
{
    AuthCallback = async tokenParams =>
    {
        // Return serialized tokenRequest string or 'IO.Ably.TokenRequest' object
        return await TokenRequestStringFromYourServer(tokenParams); // tokenRequest will be used to obtain token from ably server.
    }
};
var client = new AblyRealtime(options);

Fetching your application's stats

var stats = await client.StatsAsync();
var firstItem = stats.Items.First();
var nextStatsPage = await stats.NextAsync();

Fetching the Ably service time

DateTimeOffset time = await client.TimeAsync();

Getting the channel status

Getting the current status of a channel, including details of the current number of Publishers, Subscribers and PresenceMembers etc:

ChannelDetails details = await channel.StatusAsync();
ChannelMetrics metrics = details.Status.Occupancy.Metrics;
// Do something with 'metrics.Publishers' etc

Making explicit HTTP requests to Ably Rest Endpoints / Batch publish

    var objectPayload = new
    {
        channels = new[] { "channel1", "channel2", "channel3", "channel4" },
        messages = new[]
        {
            new
            {
                name = "eventName",
                data = "foo",
            }
        }
    };
  var jsonPayload = JsonConvert.SerializeObject(objectPayload);
  var paginatedResponse = await ablyRest.RequestV2(HttpMethod.Post, "/messages", null, jsonPayload, null);

Examples

Sample .NET Core implementation

using System;

using IO.Ably;

namespace testing_ably_console
{
    class Program
    {
        static async Task Main(string[] args)
        {
            Console.WriteLine("Hello World!");
            var realtime = new AblyRealtime("<api key>");
            IRealtimeChannel channel = realtime.Channels.Get("test");
            await channel.PublishAsync("greeting", "Hello World!");
            Console.WriteLine("Farewell World!");
        }
    }
}

Sample .NET Framework implementation (when you don't have async main method)*

using System;

using IO.Ably;

namespace testing_ably_console
{
    class Program
    {
        static void Main(string[] args)
        {
            MainAsync(args).GetAwaiter().GetResult();
        }

        static async Task MainAsync(string[] args)
        {
            Console.WriteLine("Hello World!");
            var realtime = new AblyRealtime("<api key>");
            IRealtimeChannel channel = realtime.Channels.Get("test");
            await channel.PublishAsync("greeting", "Hello World!");
        }
    }
}

MAUI configuration

<ItemGroup>
  <TrimmerRootAssembly Include="IO.Ably" />
</ItemGroup>

Push notification

The Ably.net library fully supports Ably's push notifications. The feature set consists of two distinct areas: Push Admin, Device Push Notifications.

The Push Notifications Readme describes:

Known Limitations

Dependencies

This library has dependencies that can differ depending on the target platform. See the nuget page for specifics.

Support, feedback and troubleshooting

Please visit https://ably.com/support for access to our knowledge-base and to ask for any assistance.

You can also view the community reported GitHub issues.

Contributing

For guidance on how to contribute to this project, see CONTRIBUTING.md.