Key Changes from v1:
- Now on V2.0.3 ! ๐
- Added Support for .NET 5,6,7,8, 9 and .NET MAUI.
- Replaced previous web client with System.Net.WebSockets.Client as I believe is better.
- Replaced Subscriptions and callbacks with System.Reactive for better handling of events.
- Added a YouTube video for a full walkthrough of the SDK.
- Added a full ReadMe for the SDK.
- It now works as expected (sorry guys ๐ )
You May want to read the Full WIKI Here (it has tones of examples)
Here is the full ReadMe;
Since I Updated this based on my MAUI Projects, I had to update my fork of Parse SDK to have MAUI support, thus
Thanks :) to:
- JonMcPherson for original Parse Live Query dotnet code.
- Parse Community for Parse SDK.
- My Parse-SDK fork which this depends on.
Live Query provides real-time data sync between server and clients over WebSockets. When data changes on the server, subscribed clients are instantly notified.
- Ensure Live Query is enabled for your classes in the Parse Dashboard.
using Parse; // Parse
using Parse.LiveQuery; // ParseLQ
using System.Reactive.Linq; // For Rx
using System.Linq; // For LINQ
using System.Collections.ObjectModel;
// Check internet
if (Connectivity.NetworkAccess != NetworkAccess.Internet)
{
Console.WriteLine("No Internet, can't init ParseClient.");
return;
}
// Init ParseClient
var client = new ParseClient(new ServerConnectionData
{
ApplicationID = "YOUR_APP_ID",
ServerURI = new Uri("YOUR_SERVER_URL"),
Key = "YOUR_CLIENT_KEY" // or MasterKey
}, new HostManifestData
{
Version = "1.0.0",
Identifier = "com.yourcompany.yourmauiapp",
Name = "MyMauiApp"
});
client.Publicize(); // Access via ParseClient.Instance globally
//I Will Just leave all this code in Docs because believe it or not, sometimes even I forget how to use my own lib :D
void SetupLiveQuery()
{
try
{
var query = ParseClient.Instance.GetQuery("TestChat");
var subscription = LiveClient.Subscribe(query);
LiveClient.ConnectIfNeeded();
// Rx event streams
LiveClient.OnConnected
.Subscribe(_ => Debug.WriteLine("LiveQuery connected."));
LiveClient.OnDisconnected
.Subscribe(info => Debug.WriteLine(info.userInitiated
? "User disconnected."
: "Server disconnected."));
LiveClient.OnError
.Subscribe(ex => Debug.WriteLine("LQ Error: " + ex.Message));
LiveClient.OnSubscribed
.Subscribe(e => Debug.WriteLine("Subscribed to: " + e.requestId));
// Handle object events (Create/Update/Delete)
LiveClient.OnObjectEvent
.Where(e => e.subscription == subscription)
.Subscribe(e =>
{
Debug.WriteLine($"Message before {Message?.Length}");
TestChat chat = new();
var objData = (e.objectDictionnary as Dictionary<string, object>);
switch (e.evt)
{
case Subscription.Event.Enter:
Debug.WriteLine("entered");
break;
case Subscription.Event.Leave:
Debug.WriteLine("Left");
break;
case Subscription.Event.Create:
chat = ObjectMapper.MapFromDictionary<TestChat>(objData);
Messages.Add(chat);
break;
case Subscription.Event.Update:
chat = ObjectMapper.MapFromDictionary<TestChat>(objData);
var obj = Messages.FirstOrDefault(x => x.UniqueKey == chat.UniqueKey);
Messages.RemoveAt(Messages.IndexOf(obj));
Messages.Add(chat);
break;
case Subscription.Event.Delete:
chat = ObjectMapper.MapFromDictionary<TestChat>(objData);
var objj = Messages.FirstOrDefault(x => x.UniqueKey == chat.UniqueKey);
Messages.RemoveAt(Messages.IndexOf(objj));
break;
default:
break;
}
Debug.WriteLine($"Message after {Message.Length}");
Debug.WriteLine($"Event {e.evt} on object {e.objectDictionnary.GetType()}");
});
}
catch (Exception ex)
{
Debug.WriteLine("SetupLiveQuery Error: " + ex.Message);
}
}
public static class ObjectMapper
{
/// <summary>
/// Maps values from a dictionary to an instance of type T.
/// Logs any keys that don't match properties in T.
///
/// Helper to Map from Parse Dictionnary Response to Model
/// Example usage TestChat chat = ObjectMapper.MapFromDictionary<TestChat>(objData);
/// </summary>
public static T MapFromDictionary<T>(IDictionary<string, object> source) where T : new()
{
// Create an instance of T
T target = new T();
// Get all writable properties of T
var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.CanWrite)
.ToDictionary(p => p.Name, p => p, StringComparer.OrdinalIgnoreCase);
// Track unmatched keys
List<string> unmatchedKeys = new();
foreach (var kvp in source)
{
if (properties.TryGetValue(kvp.Key, out var property))
{
try
{
// Convert and assign the value to the property
if (kvp.Value != null && property.PropertyType.IsAssignableFrom(kvp.Value.GetType()))
{
property.SetValue(target, kvp.Value);
}
else if (kvp.Value != null)
{
// Attempt conversion for non-directly assignable types
var convertedValue = Convert.ChangeType(kvp.Value, property.PropertyType);
property.SetValue(target, convertedValue);
}
}
catch (Exception ex)
{
Console.WriteLine($"Failed to set property {property.Name}: {ex.Message}");
}
}
else
{
// Log unmatched keys
unmatchedKeys.Add(kvp.Key);
}
}
// Log keys that don't match
if (unmatchedKeys.Count > 0)
{
Debug.WriteLine("Unmatched Keys:");
foreach (var key in unmatchedKeys)
{
Debug.WriteLine($"- {key}");
}
}
return target;
}
}
This v2 version integrates LINQ and Rx.NET, enabling highly flexible and reactive real-time data flows with Parse Live Queries. Advanced filtering, buffering, throttling, and complex transformations are now possible with minimal code.
PRs are welcome!