Realm Blog

Announcing Realm .NET: Leveling Up Our Microsoft Support with Global Notifier & UWP Sync

The Realm Platform helps you build cross-platform native applications, with sophisticated reactive features, realtime data sync, and robust offline experiences. It has two foundational components – the Realm database, an embedded client-side live object database, and the Realm Object Server, a realtime synchronization and event handling server – and these have worked great for anyone handy with Linux or Mac, Node.JS, and PostgreSQL. But, if you were a .NET stack developer, with a preference for C# and Windows, and maybe some important data in SQL Server, Realm only met you halfway. You could use Realm Xamarin to build native client-side apps with C#, and you could rely on our .NET Core support to start using Realm in server-side applications, but most of the rest of Realm required you to work outside of the standard Microsoft stack.

Until today. We’re pleased to announce Realm .NET, with robust support for critical Microsoft stack technology across the Realm Platform. We’re shipping three new things that are available today: the new Global Notifier for .NET, new sync on Windows/UWP, and data encryption support for Windows/UWP. In addition, we’re announcing a new SQL Server Connector, making two-way data integration between Realm and SQL Server dead simple, and giving mobile dev teams a much better way to bring legacy databases into their realtime, offline-ready applications. The SQL Server Connector is in private beta today, but will be available to Enterprise Edition customers beginning in December.

Demos speak louder than words, so we’ve created a simple demo video as a quick introduction. Then read on for a deep dive focused on the Global Notifier, and find more details on Realm .NET in our docs.

 

Technical Deep Dive

When we shipped .NET Core support in the summer, we enabled you to start using Realms in your server-side applications. This made it effortless to distribute new data to all your connected clients by just writing it to the user’s Realm on the server and letting Realm handle the synchronization for you. Reacting to changes by users wasn’t that easy though, as when you have thousands of user-Realms, any of which can change at any given time, keeping an open instance for each one of them is simply not feasible. This is where the Global Notifier steps in – it is heavily optimized to observe huge amounts of Realms and notify you whenever any of them changes.

Essentially, the Global Notifier is a NuGet package you add to your .NET project (it can be either .NET Core or a classic .NET Framework project, such as Asp.Net, console app, or even WPF app). Then, you implement the INotificationHandler interface that exposes two methods that are called by the SDK itself: ShouldHandle allows you to specify which Realm paths you’re interested in observing. HandleChangesAsync will be invoked when an observed Realm changes with detailed information about the state of the Realm just before and just after the change. Finally, you start the notifier by calling Notifier.StartAsync) and pass it all the notification handlers you want to register. And the cool part is, you can deploy this app anywhere you want – it doesn’t have to sit on the same instances that are running your Realm Object Server.

Let’s see how we could use it to create a handler that will analyze the sentiment of a support ticket by using Azure’s Text Analytics API. If you want to skip the step-by-step tutorial, jump right ahead to the completed sample.

Prerequisites

Before we start, you’ll need:

Setting Up the Project

Create a .NET Core app and add the following NuGet packages:

Setting Up the Notification Handler

The next step is to implement a notification handler. We’ll use the built-in RegexNotificationHandler as it provides an easy implementation of the ShouldHandle method in terms of Regex matching. We can handle all user realms at the /~/feedback path by using the ^/.*/feedback$ regex:

public class FeedbackHandler : RegexNotificationHandler
{
    private readonly SentimentClient _sentimentClient;
    public FeedbackHandler() : base($"^/.*/feedback$")
    {
        _sentimentClient = new SentimentClient(your-api-key)
        {
            Url = https://YOUR-REGION.api.cognitive.microsoft.com/text/analytics/v2.0/sentiment”
        };
    }

    public override async Task HandleChangeAsync(IChangeDetails details)
    {
        // We’ll populate this in a bit
    }
}

We’ve created the basic skeleton and added the sentiment client, now we only need to start using it. To add some context, let’s assume that the objects the client is inserting and we’re handling have the following structure:

public class Ticket : RealmObject
{
    public string Description { get; set; }

    public float Score { get; set; }

    // Other irrelevant properties
}

Then, the first step toward implementing HandleChangeAsync will be to check if we have any insertions for that class:

if (!details.Changes.TryGetValue("Ticket", out var changeSetDetails) ||
    changeSetDetails.Insertions.Length == 0)
{
    // We don’t care about other objects being updated
    return;
}

Then, we can construct the SentimentRequest that we’ll send to Azure for analysis:

var request = new SentimentRequest
{
    Documents = changeSetDetails.Insertions
                                .Select((obj, index) => new SentimentDocument
                                {
                                    Id = index.ToString(),
                                    Text = obj.CurrentObject.Description,
                                    Language = "en"
                                })
                                .Cast<IDocument>()
                                .ToList()
};

What this is doing is sending the Description value of our Tickets as the text we’ll analyze and assigning the consecutive index of the object in the array as the Id of the document we’re analyzing. The document Id needs to be unique just for the request scope, so that approach is perfectly fine.

Next, we’ll send the request and process the response:

var response = await _sentimentClient.GetSentimentAsync(request);
var analyzedDocuments = response.Documents.Select(d =>
{
    var ticket = changeSetDetails.Insertions[int.Parse(d.Id)];
    return new
    {
        Score = d.Score,
        TicketReference = ThreadSafeReference.Create(ticket.CurrentObject)
    };
});

What’s interesting here is that we’re creating a ThreadSafeReference for the object. The reason is that, even though we’ll change it on the same thread, details.CurrentRealm, to which ticket.CurrentObject belongs, is read-only (as it is pinned to a specific version and we wouldn’t want things to change while processing the changes) and we’ll open a different Realm instance to write the changes to the ticket. To obtain the same object in a different instance of the same Realm, we can pass the object by its primary key or use ThreadSafeReference which works even for objects without a primary key.

Finally, we can write the score to the tickets:

using (var realm = details.GetRealmForWriting())
{
    foreach (var doc in analyzedDocuments)
    {
        var reference = realm.ResolveReference(doc.TicketReference);
        realm.Write(() => reference.Score = doc.Score);
    }
}

Starting Up the Notification Handler

To start up the notification handler we just created, we’ll need to pass it to a NotifierConfiguration. But before that, we’ll need to login an admin user:

SyncConfiguration.SetFeatureToken(YOUR-ROS-FEATURE-TOKEN);

var credentials = Credentials.UsernamePassword(username, password, createUser: false);
var admin = await User.LoginAsync(credentials, new Uri("http://127.0.0.1:9080"));

Next, we create the configuration:

var config = new NotifierConfiguration(admin)
{
    Handlers = { new FeedbackHandler() },
};

And finally, we start the notifier:

var notifier = await Notifier.StartAsync(config);

Make sure to keep a reference to the notifier for as long as you wish to receive notifications. When you want to stop your application, make sure to dispose of it to avoid any native memory leaks.

Seeing It in Action

To test it out, you can create a small console app that opens a regular synchronized Realm and adds Ticket objects. Something like:

var config = new SyncConfiguration(user, new Uri(http://127.0.0.1:9080/~/feedback”));
using (var realm = Realm.GetInstance(config))
{
    while (true)
    {
        var input = Console.ReadLine();
        If (input == exit)
        {
            break;
        }

        realm.Write(() => realm.Add(new Ticket
        {
            Description = input
        }));
    }
}

Completed Sample

You can find the complete sample, along with a user and employee apps in our Feedback Manager repo.


Realm Team

At Realm, our mission is to help developers build better apps faster. We provide a unique set of tools and platform technologies designed to make it easy for developers to build apps with sophisticated, powerful features — things like realtime collaboration, augmented reality, live data synchronization, offline experiences, messaging, and more.

Everything we build is developed with an eye toward enabling developers for what we believe the mobile internet evolves into — an open network of billions of users and trillions of devices, and realtime interactivity across them all.

Get more development news like this