{"id":20770682,"url":"https://github.com/ably/ably-dotnet","last_synced_at":"2025-04-30T13:53:25.780Z","repository":{"id":23993816,"uuid":"27377333","full_name":"ably/ably-dotnet","owner":"ably","description":".NET, MAUI, Xamarin, Mono and Unity client library SDK for Ably realtime messaging service","archived":false,"fork":false,"pushed_at":"2024-08-29T17:14:13.000Z","size":31939,"stargazers_count":46,"open_issues_count":74,"forks_count":22,"subscribers_count":31,"default_branch":"main","last_synced_at":"2025-04-10T22:04:58.217Z","etag":null,"topics":["c-sharp","client-library","csharp","dotnet","maui","maui-app","mono","realtime","realtime-messaging","rest","sdk","unity","uwp","xamarin"],"latest_commit_sha":null,"homepage":"https://ably.com/download","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ably.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2014-12-01T11:56:18.000Z","updated_at":"2024-11-19T16:48:20.000Z","dependencies_parsed_at":"2023-10-14T16:39:59.648Z","dependency_job_id":"426707f1-c92f-4048-8322-4168220f40d2","html_url":"https://github.com/ably/ably-dotnet","commit_stats":{"total_commits":3200,"total_committers":23,"mean_commits":139.1304347826087,"dds":0.6112500000000001,"last_synced_commit":"d7c9c85ddb745b7cee3c3d6c187f02607131e274"},"previous_names":[],"tags_count":60,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ably%2Fably-dotnet","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ably%2Fably-dotnet/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ably%2Fably-dotnet/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ably%2Fably-dotnet/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ably","download_url":"https://codeload.github.com/ably/ably-dotnet/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251715114,"owners_count":21631839,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["c-sharp","client-library","csharp","dotnet","maui","maui-app","mono","realtime","realtime-messaging","rest","sdk","unity","uwp","xamarin"],"created_at":"2024-11-17T12:11:21.870Z","updated_at":"2025-04-30T13:53:25.758Z","avatar_url":"https://github.com/ably.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ably-dotnet\n\n[![NuGet version](https://badge.fury.io/nu/ably.io.svg)](https://badge.fury.io/nu/ably.io)\n[![Windows - build and test](https://github.com/ably/ably-dotnet/actions/workflows/build-and-test-windows.yml/badge.svg)](https://github.com/ably/ably-dotnet/actions/workflows/build-and-test-windows.yml)\n[![MacOS - build and test](https://github.com/ably/ably-dotnet/actions/workflows/build-and-test-macos.yml/badge.svg)](https://github.com/ably/ably-dotnet/actions/workflows/build-and-test-macos.yml)\n[![Linux - build and test](https://github.com/ably/ably-dotnet/actions/workflows/build-and-test-linux.yml/badge.svg)](https://github.com/ably/ably-dotnet/actions/workflows/build-and-test-linux.yml)\n[![Features](https://github.com/ably/ably-dotnet/actions/workflows/features.yml/badge.svg)](https://github.com/ably/ably-dotnet/actions/workflows/features.yml)\n\n_[Ably](https://ably.com) 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](https://ably.com/docs)._\n\nThis 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](https://sdk.ably.com/builds/ably/ably-dotnet/main/features/).\n\n## Supported platforms\n\n* [.NET Standard 2.0+](https://learn.microsoft.com/en-us/dotnet/standard/net-standard?tabs=net-standard-2-0)\n* .NET 6.0+, MAUI, check [MAUI config](#maui-configuration).\n* .NET Framework 4.6.2+\n* .NET (Core) 2.0+\n* Mono 5.4+\n* [Xamarin.Android 8.0+](https://developer.xamarin.com/releases/android/xamarin.android_8/xamarin.android_8.0/)\n* [Xamarin.iOS 10.14+](https://developer.xamarin.com/releases/ios/xamarin.ios_10/xamarin.ios_10.14/)\n* Xamarin.Mac 3.8+\n* Unity 2019.x +, [check README](./unity/README.md)\n* Push Notification, [check support section](#push-notification).\n\n## Documentation\n\nVisit [https://ably.com/docs](https://ably.com/docs) for a complete API reference and more examples.\n\n## Installation\n\nThe client library is available as a [nuget package](https://www.nuget.org/packages/ably.io/).\n\nYou can install it from the Package Manager Console:\n\n```shell\nPM\u003e Install-Package ably.io\n```\n\nor using the .NET CLI in your project directory:\n\n```shell\ndotnet add package ably.io\n```\n\n## Using the Realtime API\n\nThe 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](https://faqs.ably.com/should-i-use-the-rest-or-realtime-library).\n\n### Instancing a Realtime client\n\nCreating a Realtime client:\n\n```csharp\n// Using basic auth with API key\n// Note in production, an API key should not be used in untrusted mobile/browser clients\nvar realtime = new AblyRealtime(\"\u003capi key\u003e\");\n```\n\n```csharp\n// Using token auth with token string\n// Note this token is not renewable - a token callback should be used in production\nvar realtime = new AblyRealtime(new ClientOptions { Token = \"token\" });\n```\n\nIf you do not have an API key, [sign up for a free API key now](https://ably.com/signup)\n\n### Connection\n\nConnecting and observing connection state changes. By default the library automatically initializes a connection.\n\n```csharp\nrealtime.Connection.On(ConnectionEvent.Connected, args =\u003e\n{\n    // Do stuff  \n});\n```\n\nTo disable the default automatic connect behavior of the library, set `AutoConnect = false` when initializing the client.\n\n```csharp\nvar realtime = new AblyRealtime(new ClientOptions(\"\u003capi key\u003e\") { AutoConnect = false });\n// Some code\nrealtime.Connect();\n```\n\nSubscribing to connection state changes and observing errors:\n\n```csharp\nrealtime.Connection.On(args =\u003e\n{\n    var currentState = args.Current; // Current state the connection transitioned to\n    var previousState = args.Previous; // Previous state\n    var error = args.Reason; // If the connection error-ed the Reason object will be populated.\n});\n```\n\n### Subscribing to a channel\n\n[Channels](https://ably.com/docs/realtime/channels?lang=javascript) are the medium through which messages are distributed. \n\nTo create a channel object:\n\n```csharp\nIRealtimeChannel channel = realtime.Channels.Get(\"test\");\n```\n\nSubscribe to all events published on that channel:\n\n```csharp\nchannel.Subscribe(message =\u003e\n{\n    var name = message.Name;\n    var data = message.Data;\n});\n```\n\nSubscribing to specific events:\n\n```csharp\nchannel.Subscribe(\"myEvent\", message =\u003e\n{\n    var name = message.Name;\n    var data = message.Data;\n});\n```\n\nObserving channel state changes and errors:\n\n```csharp\nchannel.On(args =\u003e\n{\n    var state = args.NewState; // Current channel State\n    var error = args.Error; // If the channel error-ed it will be reflected here\n});\n```\n\nor\n\n```csharp\nchannel.On(ChannelState.Attached, args =\u003e\n{\n    // Do stuff when channel is attached\n});\n```\n\n### Publishing to a channel\n\nThe client support a callback and async publishing. The simplest way to publish is:\n\n```csharp\nchannel.Publish(\"greeting\", \"Hello World!\");\n```\n\nwith a callback:\n\n```csharp\nchannel.Publish(\"greeting\", \"Hello World!\", (success, error) =\u003e\n{\n    // If publish succeeded 'success' is 'true'\n    // if publish failed 'success' is 'false' and 'error' will contain the specific error\n});\n```\n\nand the async version which if you `await` it will complete when the message has been acknowledged or rejected by the Ably service:\n\n```csharp\nvar result = await channel.PublishAsync(\"greeting\", \"Hello World!\");\n// You can check if the message failed\nif (result.IsFailure)\n{\n    var error = result.Error; // The error reason can be accessed as well\n}\n```\n\n- When calling `channel.Publish` with a dotnet object instance as the message data, the library will use the Newtonsoft Json.NET library to serialize the message with default serialization settings.\n- If you need to use custom seralization settings, you can apply the serialization yourself and send the resulting string as the message data:\n\n```csharp\nvar serializedData = JsonConvert.SerializeObject(message,\n    new JsonSerializerSettings\n    {\n        ContractResolver = new CamelCasePropertyNamesContractResolver()\n    });\n\nvar ablyMessage = new Message(\"name\", serializedData) {Encoding = \"json\"};\n\nchannel.Publish(ablyMessage);\n```\n\n### Getting channel history\n\nCalling history returns a paginated list of message. The object is of type `PaginatedResult\u003cMessage\u003e` and can be iterated through as a normal list.  \n\n```csharp\nvar history = await channel.HistoryAsync();\n// Loop through current history page\nforeach (var message in history.Items)\n{\n    // Do something with message\n}\n// Get next page.\nvar nextPage = await history.NextAsync();\n```\n\n### Getting presence history\n\nGetting presence history is similar to how message history works. You get back `PaginatedResult\u003cPresenceMessage\u003e` and can navigate or iterate through the page\n\n```csharp\nvar presenceHistory = await channel.Presence.HistoryAsync();\n// Loop through the presence messages\nforeach (var presence in presenceHistory.Items)\n{\n    // Do something with the messages\n}\n\nvar presenceNextPage = await presenceHistory.NextAsync();\n```\n\n### Subscribing to a channel in delta mode\n\nSubscribing to a channel in delta mode enables [delta compression](https://ably.com/docs/realtime/channels/channel-parameters/deltas). 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.\n\nRequest a `Vcdiff` formatted delta stream using channel options when you get the channel:\n\n```csharp\nvar channelParams = new ChannelParams();\nchannelParams.Add(\"delta\", \"vcdiff\");\nvar channelOptions = new ChannelOptions();\nchannelOptions.Params = channelParams;\nIRealtimeChannel channel = ably.Channels.Get(ChannelName, channelOptions);\n```\n\nBeyond 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\u003cMessage\u003e` handler continue to contain the values that were originally published.\n\nIf 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\"`.\n\n### Symmetric end-to-end encrypted payloads on a channel\n\nWhen 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.\n\n```csharp\nvar secret = Crypto.GetRandomKey();\nvar encryptedChannel = realtime.Get(\"encrypted\", new ChannelOptions(secret));\nencryptedChannel.Subscribe(message =\u003e\n{\n    var data = message.data; // Sensitive data (encrypted before published)\n});\nencryptedChannel.Publish(\"name (not encrypted)\", \"sensitive data (encrypted before published)\");\n```\n\n### Executing callbacks on Main/UI thread\n- The library creates a number of threads and all listeners/callbacks are executed on non-UI/background threads.\n- To execute listeners/callbacks on the Main/UI thread, we support capturing the `SynchronizationContext`.\n\n```\noptions.CustomContext = SynchronizationContext.Current;\n```\n\n### Increase Transport send and receive buffers\n\nIn .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`\nTo 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.\n\n```csharp\nvar maxBufferSize = 64 * 1024;\nvar options = new ClientOptions();\nvar websocketOptions = new MsWebSocketOptions() { SendBufferInBytes = maxBufferSize, ReceiveBufferInBytes = maxBufferSize };\noptions.TransportFactory = new MsWebSocketTransport.TransportFactory(websocketOptions);\nvar realtime = new AblyRealtime(options);\n```\n\n## Enable logging\n\nDefine a new class that implements `ILoggerSink` interface.\n\n```csharp\nclass CustomLogHandler : ILoggerSink\n{\n    public void LogEvent(LogLevel level, string message)\n    {\n        Console.WriteLine($\"Handler LogLevel : {level}, Data :{message}\");\n    }\n}\n```\n\nUpdate clientOptions for `LogLevel` and `LogHandler`.\n\n```csharp\nclientOpts.LogLevel = LogLevel.Debug;\nclientOpts.LogHandler = new CustomLogHandler();\n```\n\n## Using the REST API\n\nThe REST library is typically used server-side in your applications and is stateless. [Find out when you should use the REST or Realtime library](https://faqs.ably.com/should-i-use-the-rest-or-realtime-library).\n\n### Instancing a REST client\n\nCreating a REST client and channel:\n\n```csharp\nvar client = new AblyRest(\"\u003capi key\u003e\");\nIRealtimeChannel channel = client.Channels.Get(\"chat\");\n```\n\nIf you do not have an API key, [sign up for a free API key now](https://ably.com/signup)\n\n### Publishing a message to a channel\n\n```csharp\nawait channel.PublishAsync(\"name\", \"data\");\n```\n\nIf the publish is not successful an error will be thrown of type `AblyException` containing error codes and error description\n\n```csharp\ntry \n{\n    await channel.PublishAsync(\"name\", \"errorData\");\n} \ncatch(AblyException ablyError) \n{\n    // Log error\n}\n```\n\n- When calling `channel.PublishAsync` with a dotnet object instance as the message data, the library will use the Newtonsoft Json.NET library to serialize the message with default serialization settings.\n- If you need to use custom seralization settings, you can apply the serialization yourself and send the resulting string as the message data:\n\n```csharp\nvar serializedData = JsonConvert.SerializeObject(message,\n    new JsonSerializerSettings\n    {\n        ContractResolver = new CamelCasePropertyNamesContractResolver()\n    });\n\nvar ablyMessage = new Message(\"name\", serializedData) {Encoding = \"json\"};\n\nchannel.Publish(ablyMessage);\n```\n\n\n### Querying channel history\n\n```csharp\nvar historyPage = await channel.HistoryAsync();\nforeach (var message in historyPage.Items)\n{\n    // Do something with each message\n}\n// Get the next page\nvar nextHistoryPage = await historyPage.NextAsync();\n```\n\n### Current presence members on a channel\n\n```csharp\nvar presence = await channel.Presence.GetAsync();\nvar first = presence.Items.FirstOrDefault();\nvar clientId = first.clientId; // 'clientId' of the first member present\nvar nextPresencePage = await presence.NextAsync();\nforeach (var presenceMessage in nextPresencePage.Items)\n{\n    // Do stuff with next page presence messages\n}\n```\n\n### Querying the presence history\n\n```csharp\n// Presence history\nvar presenceHistory = await channel.Presence.HistoryAsync();\nforeach (var presenceMessage in presenceHistory.Items)\n{\n    // Do stuff with presence messages\n}\n\nvar nextPage = await presenceHistory.NextAsync();\nforeach (var presenceMessage in nextPage.Items)\n{\n    // Do stuff with next page messages\n}\n```\n\n### Authentication\n- It is recommended to use `ABLY_KEY` at server side. Check [official ably auth documentation](https://ably.com/docs/auth) for more info.\n- `ABLY_KEY` should not be exposed at client side where it can be used for malicious purposes.\n- Server can use `ABLY_KEY` for initializing the `AblyRest` instance.\n\n```csharp\nvar rest = new AblyRest(\"API_KEY\");\n```\n- Token requests are issued by your servers and signed using your private API key. \n- 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.\n```csharp\n// e.g. ASP.Net server endpoint\napp.MapGet(\"/token\", async() =\u003e {\n    string tokenRequest = await rest.Auth.CreateTokenRequestAsync();\n    return Content(tokenRequest, \"application/json\"); // make sure to set `contentType` as json.\n});\n```\n- You can also return JWT string token signed using `ABLY_KEY` as per [official ably JWT doc](https://ably.com/tutorials/jwt-authentication). \n\n### Using the Token auth at client side\n\n- Create `ClientOptions` instance with `AuthCallback` property\n\n```csharp\nvar options = new ClientOptions\n{\n    AuthCallback = async tokenParams =\u003e\n    {\n        // Return serialized tokenRequest string or 'IO.Ably.TokenRequest' object\n        return await TokenRequestStringFromYourServer(tokenParams); // tokenRequest will be used to obtain token from ably server.\n    }\n};\nvar client = new AblyRealtime(options);\n```\n- If JWT token is returned by server \n```csharp\nvar options = new ClientOptions\n{\n    AuthCallback = async tokenParams =\u003e\n    {\n        string jwtToken = await getJwtTokenFromServer(tokenParams);\n        int expiresIn = 3600; // assuming jwtToken has 1 hr expiry\n        return new TokenDetails(jwtToken) { \n            Expires = DateTimeOffset.UtcNow.AddSeconds(expiresIn) \n        }; // jwt token will directly be used to authenticate with ably server.\n    }\n};\n```\n- Check [official token auth documentation](https://ably.com/docs/auth/token?lang=csharp) for more information.\n\n### Fetching your application's stats\n\n```csharp\nvar stats = await client.StatsAsync();\nvar firstItem = stats.Items.First();\nvar nextStatsPage = await stats.NextAsync();\n```\n\n### Fetching the Ably service time\n\n```csharp\nDateTimeOffset time = await client.TimeAsync();\n```\n\n### Getting the channel status\n\nGetting the current status of a channel, including details of the current number of `Publishers`, `Subscribers` and `PresenceMembers` etc:\n\n```csharp\nChannelDetails details = await channel.StatusAsync();\nChannelMetrics metrics = details.Status.Occupancy.Metrics;\n// Do something with 'metrics.Publishers' etc\n```\n\n### Making explicit HTTP requests to Ably Rest Endpoints / Batch publish\n- The `AblyRest-\u003eRequest` method can be used to make explicit HTTP requests to the [Ably REST API](https://ably.com/docs/api/rest-api).\n- It automatically adds necessary auth headers based on the initial auth config and supports pagination.\n- The following is an example of using the batch publish API based on the [Ably batch publish rest endpoint documentation](https://ably.com/docs/api/rest-api#batch-publish).\n\n```csharp\n    var objectPayload = new\n    {\n        channels = new[] { \"channel1\", \"channel2\", \"channel3\", \"channel4\" },\n        messages = new[]\n        {\n            new\n            {\n                name = \"eventName\",\n                data = \"foo\",\n            }\n        }\n    };\n  var jsonPayload = JsonConvert.SerializeObject(objectPayload);\n  var paginatedResponse = await ablyRest.RequestV2(HttpMethod.Post, \"/messages\", null, jsonPayload, null);\n```\n- See the [ably rest endpoint doc](https://ably.com/docs/api/rest-api) for more information on other endpoints.\n\n## Examples\n\n* More Examples can be found under ```examples``` directory.\n* While working with console app, make sure to put explicit await for async methods.\n\n### Sample .NET Core implementation\n\n```csharp\nusing System;\n\nusing IO.Ably;\n\nnamespace testing_ably_console\n{\n    class Program\n    {\n        static async Task Main(string[] args)\n        {\n            Console.WriteLine(\"Hello World!\");\n            var realtime = new AblyRealtime(\"\u003capi key\u003e\");\n            IRealtimeChannel channel = realtime.Channels.Get(\"test\");\n            await channel.PublishAsync(\"greeting\", \"Hello World!\");\n            Console.WriteLine(\"Farewell World!\");\n        }\n    }\n}\n```\n\n### Sample .NET Framework implementation (when you don't have async main method)*\n\n```csharp\nusing System;\n\nusing IO.Ably;\n\nnamespace testing_ably_console\n{\n    class Program\n    {\n        static void Main(string[] args)\n        {\n            MainAsync(args).GetAwaiter().GetResult();\n        }\n\n        static async Task MainAsync(string[] args)\n        {\n            Console.WriteLine(\"Hello World!\");\n            var realtime = new AblyRealtime(\"\u003capi key\u003e\");\n            IRealtimeChannel channel = realtime.Channels.Get(\"test\");\n            await channel.PublishAsync(\"greeting\", \"Hello World!\");\n        }\n    }\n}\n```\n\n## MAUI configuration\n- Since `ably-dotnet` makes use of the reflection API, MAUI assembly trimming may cause issues.\n- When using MAUI, we recommend adding the following to your `.csproj` file to disable assembly trimming.\n\n```xml\n\u003cItemGroup\u003e\n  \u003cTrimmerRootAssembly Include=\"IO.Ably\" /\u003e\n\u003c/ItemGroup\u003e\n```\n- For more information related to assembly trimming, check [MAUI trimming doc](https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/trimming-options).\n- To resolve issues related to iOS signed release, [update csproj config](https://github.com/ably/ably-dotnet/issues/1259#issuecomment-1723307985)\n\n## Push notification\n\nThe Ably.net library fully supports Ably's push notifications. The feature set consists of two distinct areas: [Push Admin](https://ably.com/docs/general/push/admin), [Device Push Notifications](https://ably.com/docs/realtime/push).\n\nThe [Push Notifications Readme](PushNotifications.md) describes:\n\n* How to setup Push notifications for Xamarin mobile apps.\n* How to use the Push Admin api to send push notifications directly to a devices or a client.\n* How to subscribe to channels that support push notification.\n* How to send Ably messages that include a notification.\n\n### Known Limitations\n* Browser push notifications in [Blazor](https://dotnet.microsoft.com/en-us/apps/aspnet/web-apps/blazor) are not supported.\n\n\n## Dependencies\n\nThis library has dependencies that can differ depending on the target platform.\nSee [the nuget page](http://nuget.org/packages/ably.io/) for specifics.\n\n## Support, feedback and troubleshooting\n\nPlease visit `https://ably.com/support` for access to our knowledge-base and to ask for any assistance.\n\nYou can also view the [community reported GitHub issues](https://github.com/ably/ably-dotnet/issues).\n\n## Contributing\n\nFor guidance on how to contribute to this project, see [CONTRIBUTING.md](CONTRIBUTING.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fably%2Fably-dotnet","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fably%2Fably-dotnet","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fably%2Fably-dotnet/lists"}