Getting Started with the Hazelcast .NET Client

What You’ll Learn

In this tutorial you will see how to connect with the Hazelcast .NET client and manipulate a map.

Before you Begin

Start a Hazelcast Viridian Cloud Cluster

  1. Sign up for a Hazelcast Viridian Cloud account (free trial is available).

  2. Log in to your Hazelcast Viridian Cloud account and start your trial by filling in the welcome questionnaire.

  3. A Viridian cluster will be created automatically when you start your trial.

  4. Press the Connect Cluster dialog and switch over to the Advanced setup tab for connection information needed below.

  5. From the Advanced setup tab, download the keystore files and take note of your Cluster ID, Discovery Token and Password as you will need them later.

Setup a Hazelcast Client

Create a new folder and navigate to it:

mkdir hazelcast-csharp-example
cd hazelcast-csharp-example

Create a new .NET Console project using the commandline tool:

dotnet new console

Add the Hazelcast.NET Client as a dependency:

dotnet add Hazelcast.Net

Extract the keystore files you downloaded from Viridian into this directory. The files you need for this tutorial are:

client.pfx

To understand and use the client, review the .NET API documentation to better understand what is possible.

Understanding the .NET Client

The following section creates and starts a Hazelcast client with default configuration, connects to your Viridian cluster before shutting the client down at the end.

Create a C# file named “Program.cs” and put the following code inside it:

using Hazelcast;

using System;

namespace Client
{
    internal static class Program
    {
        public static async Task Main(string[] args)
        {
            // Create a client connection
            var options = new HazelcastOptionsBuilder()
                .With(config =>
                {
                    // Your Viridian cluster name.
                    config.ClusterName = "<YOUR_CLUSTER_ID>";

                    // Your discovery token to connect Viridian cluster.
                    config.Networking.Cloud.DiscoveryToken = "<YOUR_DISCOVERY_TOKEN>";

                    // Configure SSL.
                    config.Networking.Ssl.Enabled = true;
                    config.Networking.Ssl.ValidateCertificateChain = false;
                    config.Networking.Ssl.CertificatePath = "client.pfx";
                    config.Networking.Ssl.CertificatePassword = "<YOUR_CERTIFICATE_PASSWORD>";
                })
                .With(args) // Pass command line args to the client
                .Build();

            await using var client = await HazelcastClientFactory.StartNewClientAsync(options);

            // take actions
        }
}

Understanding the Hazelcast SQL API

Hazelcast SQL API is a Calcite SQL based interface to allow you to interact with Hazelcast much like any other datastore.

In the following example, we will create a map and insert into it, entries where the keys are ids and the values are defined as an object representing a city.

using Hazelcast;
using Hazelcast.Serialization.Compact;

namespace Client
{
    internal class CityDTO
    {
        public string City { get; set; }
        public string Country { get; set; }
        public int Population { get; set; }
    }

    internal class CitySerializer : ICompactSerializer<CityDTO>
    {
        public string TypeName => "CityDTO";

        public CityDTO Read(ICompactReader reader)
        {
            return new CityDTO()
            {
                City = reader.ReadString("city"),
                Country = reader.ReadString("country"),
                Population = reader.ReadInt32("population")
            };
        }

        public void Write(ICompactWriter writer, CityDTO value)
        {
            writer.WriteString("city", value.City);
            writer.WriteString("country", value.Country);
            writer.WriteInt32("population", value.Population);
        }
    }

    internal static class Program
    {
        public static async Task Main(string[] args)
        {
            // Create a client connection
            var options = new HazelcastOptionsBuilder()
                .With(config =>
                {
                    // Your Viridian cluster name.
                    config.ClusterName = "<YOUR_CLUSTER_ID>";

                    // Your discovery token to connect Viridian cluster.
                    config.Networking.Cloud.DiscoveryToken = "<YOUR_DISCOVERY_TOKEN>";

                    // Configure SSL.
                    config.Networking.Ssl.Enabled = true;
                    config.Networking.Ssl.ValidateCertificateChain = false;
                    config.Networking.Ssl.CertificatePath = "client.pfx";
                    config.Networking.Ssl.CertificatePassword = "<YOUR_CERTIFICATE_PASSWORD>";

                    // Register Compact serializer of City class.
                    config.Serialization.Compact.AddSerializer(new CitySerializer());
                })
                .With(args) // Pass command line args to the client
                .Build();

            // Connect to your Hazelcast Cluster
            await using var client = await HazelcastClientFactory.StartNewClientAsync(options);

            // Create a map on the cluster
            await CreateMapping(client);

            // Add some data
            await PopulateCities(client);

            // Output the data
            await FetchCities(client);
        }

        private static async Task CreateMapping(IHazelcastClient client)
        {
            // Mapping is required for your distributed map to be queried over SQL.
            // See: https://docs.hazelcast.com/hazelcast/latest/sql/mapping-to-maps

            Console.Write("\nCreating the mapping...");

            var mappingCommand = @"CREATE OR REPLACE MAPPING
                                    cities (
                                        __key INT,
                                        country VARCHAR,
                                        city VARCHAR,
                                        population INT) TYPE IMAP
                                    OPTIONS (
                                        'keyFormat' = 'int',
                                        'valueFormat' = 'compact',
                                        'valueCompactTypeName' = 'CityDTO')";

            await client.Sql.ExecuteCommandAsync(mappingCommand);

            Console.Write("OK.");
        }

        private static async Task PopulateCities(IHazelcastClient client)
        {
            var deleteQuery = @"DELETE FROM cities";

            var insertQuery = @"INSERT INTO cities
                                (__key, city, country, population) VALUES
                                (1, 'London', 'United Kingdom', 9540576),
                                (2, 'Manchester', 'United Kingdom', 2770434),
                                (3, 'New York', 'United States', 19223191),
                                (4, 'Los Angeles', 'United States', 3985520),
                                (5, 'Istanbul', 'Türkiye', 15636243),
                                (6, 'Ankara', 'Türkiye', 5309690),
                                (7, 'Sao Paulo ', 'Brazil', 22429800)";

            try
            {
                Console.Write("\nInserting data...");
                await client.Sql.ExecuteCommandAsync(deleteQuery);
                await client.Sql.ExecuteCommandAsync(insertQuery);
            }
            catch (Exception ex)
            {
                Console.WriteLine("FAILED. "+ex.ToString());
            }

            Console.Write("OK.");
        }

        private static async Task FetchCities(IHazelcastClient client)
        {
            Console.Write("\nFetching cities...");

            await using var result = await client.Sql.ExecuteQueryAsync("SELECT __key, this FROM cities");
            Console.Write("OK.");
            Console.WriteLine("\n--Results of 'SELECT __key, this FROM cities'");
            Console.WriteLine(String.Format("| {0,4} | {1,20} | {2,20} | {3,15} |","id", "country", "city", "population"));

            await foreach (var row in result)
            {
                var id = row.GetKey<int>();      // Corresponds to '__key'
                var c = row.GetValue<CityDTO>(); // Corresponds to 'this'

                Console.WriteLine(string.Format("| {0,4} | {1,20} | {2,20} | {3,15} |",
                                    id,
                                    c.Country,
                                    c.City,
                                    c.Population));
            }
        }
    }
}

The output of this code is given below:

Creating the mapping...OK.
Inserting data...OK.
Fetching cities...OK.
--Results of 'SELECT __key, this FROM cities'
|   id | country              | city                 | population      |
|    2 | United Kingdom       | Manchester           | 2770434         |
|    6 | Türkiye              | Ankara               | 5309690         |
|    1 | United Kingdom       | London               | 9540576         |
|    7 | Brazil               | Sao Paulo            | 22429800        |
|    4 | United States        | Los Angeles          | 3985520         |
|    5 | Türkiye              | Istanbul             | 15636243        |
|    3 | United States        | New York             | 19223191        |
Ordering of the keys is NOT enforced and results may NOT correspond to insertion order.

Understanding the Hazelcast Map API

A Hazelcast Map is a distributed key-value store, similar to C# dictionary. You can store key-value pairs in a Hazelcast Map.

In the following example, we will work with map entries where the keys are ids and the values are defined as an object representing a city.

using Hazelcast;

namespace Client
{
    internal static class Program
    {
        public static async Task Main(string[] args)
        {
            // Create a client connection
            var options = new HazelcastOptionsBuilder()
                .With(config =>
                {
                    // Your Viridian cluster name.
                    config.ClusterName = "<YOUR_CLUSTER_ID>";

                    // Your discovery token to connect Viridian cluster.
                    config.Networking.Cloud.DiscoveryToken = "<YOUR_DISCOVERY_TOKEN>";

                    // Configure SSL.
                    config.Networking.Ssl.Enabled = true;
                    config.Networking.Ssl.ValidateCertificateChain = false;
                    config.Networking.Ssl.CertificatePath = "client.pfx";
                    config.Networking.Ssl.CertificatePassword = "<YOUR_CERTIFICATE_PASSWORD>";
                })
                .With(args) // Pass command line args to the client
                .Build();

            await using var client = await HazelcastClientFactory.StartNewClientAsync(options);

            // Create a map on the cluster
            await using var citiesMap = await client.GetMapAsync<int, string>("cities");

            // Add some data
            await citiesMap.PutAsync(1, "London");
            await citiesMap.PutAsync(2, "New York");
            await citiesMap.PutAsync(3, "Tokyo");

            // Output the data
            var entries = citiesMap.GetEntriesAsync();

            foreach (var entry in entries.Result)
            {
                Console.WriteLine($"{entry.Key} -> {entry.Value}");
            }
        }
}

Following line returns a map proxy object for the cities map:

            // Create a map on the cluster
            await using var citiesMap = await client.GetMapAsync<int, string>("cities");

If cities doesn’t exist, it will be automatically created. All the clients connected to the same cluster will have access to the same map.

With these lines, client adds data to the cities map. The first parameter is the key of the entry, the second one is the value.

            // Add some data
            await citiesMap.PutAsync(1, "London");
            await citiesMap.PutAsync(2, "New York");
            await citiesMap.PutAsync(3, "Tokyo");

Then, we get the data using the GetEntriesAsync() method and iterate over the results.

            // Output the data
            var entries = citiesMap.GetEntriesAsync();

            foreach (var entry in entries.Result)
            {
                Console.WriteLine($"{entry.Key} -> {entry.Value}");
            }

The output of this code is given below:

2 -> New York
1 -> London
3 -> Tokyo
Ordering of the keys is NOT enforced and results may NOT correspond to entry order.

Summary

In this tutorial, you learned how to get started with the Hazelcast .NET Client and put data into a distributed map.

See Also

There are a lot of things that you can do with the .NET Client. For more, such as how you can query a map with predicates and SQL, check out our .NET Client repository and our .NET API documentation to better understand what is possible.

If you have any questions, suggestions, or feedback please do not hesitate to reach out to us via Hazelcast Community Slack. Also, please take a look at the issue list if you would like to contribute to the client.