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. The library currently targets the Ably 1.1-beta client library specification. You can jump to the 'Known Limitations' section to see the features this client library does not yet support or or view our client library SDKs feature support matrix to see the list of all the available features.
- .NET Standard 2.0+
- .NET 6.x, 7.x, MAUI, check MAUI config.
- .NET Framework 4.6.2+
- .NET (Core) 2.0+
- Mono 5.4+
- Xamarin.Android 8.0+
- Xamarin.iOS 10.14+
- Xamarin.Mac 3.8+
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:
- How to setup Push notifications for Xamarin mobile apps
- How to use the Push Admin api to send push notifications directly to a devices or a client
- How to subscribe to channels that support push notification
- How to send Ably messages that include a notification
Downloading Unity Package
- Please download the latest Unity package from the GitHub releases page. All releases from 1.2.4 has
.unitypackage
included. - Please take a look at importing unity package doc for initial config. and usage.
Supported Platforms
- Ably Unity SDK supports Windows, MacOS, Linux, Android and iOS.
- It doesn't support WebGL due to incompatibility with WebSockets. Read the Direct Socket Access section under WebGL Networking.
- To support WebGL, you should refer to interation with browser javascript from WebGL. You can import ably-js as a browser javascript and call it from WebGL. For more information, refer to the project Ably Tower Defence.
Note - Please take a look at Unity README and Ably Unity Blog for more information.
- Browser push notifications in Blazor are not supported.
Visit https://ably.com/docs
for a complete API reference and more examples.
The client library is available as a nuget package.
You can install it from the Package Manager Console using this command
PM> Install-Package ably.io
or using the .NET CLI in your project directory using
dotnet add package ably.io
All examples assume a client has been created as follows:
// Using basic auth with API key
var realtime = new AblyRealtime("<api key>");
// Using token auth with token string
var realtime = new AblyRealtime(new ClientOptions { Token = "token" });
If you do not have an API key, sign up for a free API key now
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.
});
Create a channel
IRealtimeChannel channel = realtime.Channels.Get("test");
Subscribing to all events:
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
});
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();
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"
.
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
}
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 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();
Getting the current status of a channel, including details of the current number of Publishers
, Subscribers
and PresenceMembers
etc is simple
ChannelDetails details = channel.Status();
ChannelMetrics metrics = details.Status.Occupancy.Metrics;
// Do something with 'metrics.Publishers' etc
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)");
- The library creates a number of threads and all listeners/callbacks are executed on non-UI/background threads.
- To execute listeners/callbacks on the Main/UI thread, we support capturing the
SynchronizationContext
.
options.CustomContext = SynchronizationContext.Current;
The rest client provides a fully async wrapper around the Ably service web api.
All examples assume a client and/or channel has been created as follows:
var client = new AblyRest("<api key>");
IRealtimeChannel channel = client.Channels.Get("test");
If you do not have an API key, sign up for a free API key now
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 historyPage = await channel.HistoryAsync();
foreach (var message in historyPage.Items)
{
// Do something with each message
}
// Get the next page
var nextHistoryPage = await historyPage.NextAsync();
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
}
// 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
}
A callback to obtain a signed TokenRequest
string or a TokenDetails
instance.
To use AuthCallback
create a ClientOptions
instance and assign an appropriate delegate to the AuthCallback
property and pass the ClientOptions
to a new AblyRealtime
instance.
var options = new ClientOptions
{
AuthCallback = async tokenParams =>
{
// Return a 'TokenDetails'/'TokenRequest' object or a token string .
// Typically this method would wrap a request to your web server.
return await GetTokenDetailsOrTokenRequestStringFromYourServer();
}
};
var client = new AblyRealtime(options);
Token requests are issued by your servers and signed using your private API key. This is the preferred method of authentication as no secrets are ever shared, and the token request can be issued to trusted clients without communicating with Ably.
TokenRequest tokenRequest = await client.Auth.CreateTokenRequestObjectAsync();
var stats = await client.StatsAsync();
var firstItem = stats.Items.First();
var nextStatsPage = await stats.NextAsync();
DateTimeOffset time = await client.TimeAsync();
- The
AblyRest->Request
method should be used to make explicit HTTP requests. - It automatically adds necessary auth headers based on the initial auth config and supports pagination.
- The following is an example of using the batch publish API based on the Ably batch publish rest endpoint documentation.
var jsonPayload =
@"{
""channels"" : [ ""channel1"", ""channel2"" ],
""messages"" : [
{
""name"": ""eventName"",
""data"" : ""message"",
}
]
}";
var paginatedResponse = await ablyRest.Request(HttpMethod.Post, "/messages", null, JObject.Parse(jsonPayload), null);
- Follow official ably rest endpoint doc for more information on other endpoints.
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);
- Since
ably-dotnet
makes use of the reflection API, MAUI assembly trimming may cause issues. - When using MAUI, we recommend adding the following to your
.csproj
file to disable assembly trimming.
<ItemGroup>
<TrimmerRootAssembly Include="IO.Ably" />
</ItemGroup>
- For more information related to assembly trimming, check MAUI trimming doc.
- More Examples can be found under
examples
directory. - While working with console app, make sure to put explicit await for async methods.
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!");
}
}
}
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!");
}
}
}
This library has dependencies that can differ depending on the target platform. See the nuget page for specifics.
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.
For guidance on how to contribute to this project, see CONTRIBUTING.md.