Swacblooms🦋

Making the Moves
Menu
  • Home
  • Motivation
  • Education
  • Programming
  • About
  • Contact
  • Privacy Policy
Home
Programming
Creating a Custom File Storage Provider for Microsoft Orleans
Programming

Creating a Custom File Storage Provider for Microsoft Orleans

Samson Amaugo June 7, 2024

Hello 👋, so in today’s article I will be writing about how I created a custom file Storage provider for Microsoft Orleans.

The purpose of this project is not to create a production ready storage provider but something I can use to easily visualize the data persistence during app development and also get a better understanding of persistence in Orleans 😅.

The image below illustrates what the custom file persistence layer looks like

Microsoft Orlans Custom File Storage Provider

To create a custom storage provider, I had to implement the IGrainStorage interface which is structured below:

public interface IGrainStorage
{
  Task ReadStateAsync<T>(string stateName, GrainId grainId, IGrainState<T> grainState);
  Task WriteStateAsync<T>(string stateName, GrainId grainId, IGrainState<T> grainState);
  Task ClearStateAsync<T>(string stateName, GrainId grainId, IGrainState<T> grainState);
}

Looking at the interface you can see that they all have similar signature, bearing “stateName“, “grainId” and “grainState” as parameters.

A Grain can have multiple states stores so the “stateName” helps to identity the particular state during data persistence from a Grain.

A Grain’s Id comprises of two important things: The type and the key.

Let’s say I have a Grain called “VotingGrain”. By default the Grain’s type is identified as “voting“

The key is the unique value sent by the client to interact with the a particular Grain. In the code below the Grain’s key is idenitified as “football“.

var soccerPolls = client.GetGrain<IVotingGrain>("football");

And finally from the IGrainStorage interface, the IGrainState<T> parameter holds a reference to the data in a Grain.

To use the Options pattern while creating the File Storage provider extension, I created the the type below:

    public class FileConfigurationOptions
    {
        // the location of the folder to use as a store
        public string? StorePath { get; set; }

        // the section in the config file to retrieve store path from
        public const string MyFileConfiguration = "FileConfiguration";
    }

Next, I created the file storage provider by implementing the IGrainStorage interface

public class FileStorage : IGrainStorage
{
    private void _ensureFileExists(string filePath)
    {
        if (!File.Exists(filePath))
        {
            var directory = Path.GetDirectoryName(filePath);
            Directory.CreateDirectory(directory!);
            File.Create(filePath).Close();
        }
    }

    private string _getFilePath(GrainId grainId, string stateName)
    {
        return Path.Combine(_options.Value.StorePath!, grainId.Type.ToString()!, grainId.Key.ToString()!, stateName + ".json");
    }
    private readonly IOptions<FileConfigurationOptions> _options;
    public FileStorage(IOptions<FileConfigurationOptions> options) => _options = options;
    public Task ClearStateAsync<T>(string stateName, GrainId grainId, IGrainState<T> grainState)
    {
        var path = _getFilePath(grainId, stateName);
        File.Delete(path);
        return Task.CompletedTask;
    }

    public async Task ReadStateAsync<T>(string stateName, GrainId grainId, IGrainState<T> grainState)
    {
        var path = _getFilePath(grainId, stateName);
        _ensureFileExists(path);
        var data = await File.ReadAllTextAsync(path);
        if (string.IsNullOrEmpty(data)) return;
        grainState.State = JsonSerializer.Deserialize<T>(data)!;
    }

    public async Task WriteStateAsync<T>(string stateName, GrainId grainId, IGrainState<T> grainState)
    {
        var path = _getFilePath(grainId, stateName);
        _ensureFileExists(path);
        var data = JsonSerializer.Serialize(grainState.State);
        await File.WriteAllTextAsync(path, data);
    }
}

The storage implementation above uses a combination of Grain’s ID Type, Grain’s ID Key and Grain’s stateName to create the file path to the file that holds the stored data.

One limitation of the storage provider mentioned above is that I didn’t use ETags to prevent concurrency issues. If the AlwaysInterleave attribute setting is enabled for a Grain’s method that writes to storage, it could cause errors due to race conditions. However, without this configuration, everything works fine 😅

Next, I created my extension method to easily help me setup the File Storage provider in Orleans. I created two methods, the first overload allows you to set the path of the “filestore” directly in the code while the second one reads the path from the Orleans configuration file.

    public static class FileStorageExtensions
    {
        public static IServiceCollection AddFileStorage(this IServiceCollection services, Action<FileConfigurationOptions> configureOptions)
        {
            services.AddOptions<FileConfigurationOptions>()
                       .Configure(configureOptions);
            return services.AddKeyedSingleton<IGrainStorage, FileStorage>("fileStateStore");
        }

        public static IServiceCollection AddFileStorage(this IServiceCollection services)
        {
            services.AddOptions<FileConfigurationOptions>()
               .Configure<IConfiguration>((settings, configuration) =>
               {
                   configuration.GetSection(FileConfigurationOptions.MyFileConfiguration).Bind(settings);
               });
            return services.AddKeyedSingleton<IGrainStorage, FileStorage>("fileStateStore");
        }
    }

You can see above that Orleans makes use of the AddKeyedSingleton feature to identify the store implementation.

And finally, I configured Orleans to use the custom file provider below:

IHostBuilder builder = new HostBuilder()
    .UseOrleans(silo =>
    {
        silo.UseLocalhostClustering();
        silo.Services.AddFileStorage();
    });

builder.ConfigureAppConfiguration((context, config) =>
{
    config.AddJsonFile("path_to_config_file.json", optional: true);
});
using IHost host = builder.Build();

await host.RunAsync();

Now when I select the store using the “fileStateStore” value in the PersistentState attribute of the Grain’s constructor, the Grain persists its data using the custom store which can be seen in the code below:

public class PersonGrain : IGrainBase, IPersonGrain
{
    IPersistentState<PersonState> state;
    public IGrainContext GrainContext { get; }
    public PersonGrain(IGrainContext context, [PersistentState("personState", "fileStateStore")] IPersistentState<PersonState> state) => (GrainContext, this.state) = (context, state);

    public async Task AddName(string name)
    {
        var context = this.GrainContext;
        state.State.Name = name;
        await state.WriteStateAsync();
    }
    public Task<string> GetName()
    {
        return Task.FromResult($"My name is {state.State.Name}");
    }
}


public class PersonState
{
    public string? Name { get; set; }
}

Thanks for reading through, the link to the project can be seen here.

To learn more about Grain persistence in Microsoft Orleans click here.

Bye 👋

Prev Article
Next Article

Related Articles

hacking your life
Have you read lots of books that revolve around motivational, …

Creating positive fallbacks by hacking your life

chatbot
My team and I participated in the Facebook DevC 2020 …

Making a Wikipedia chatbot with WIT.AI

About The Author

Samson Amaugo

I am Samson Amaugo. I am a full-stack developer and I specialize in DotNet and the MERN stack.

45 Comments

  1. Fredrick

    I wish to understand more.

    June 7, 2024
  2. You have a transfer from user. Gо tо withdrаwаl =>> https://telegra.ph/Go-to-your-personal-cabinet-08-25?hs=ff2c0199392d56e5efbc7aea78485b46&

    dqksnf

    September 28, 2024
  3. Ticket- TRANSACTION 1,82387 BTC. Go to withdrawal >> https://telegra.ph/Go-to-your-personal-cabinet-08-25?hs=ff2c0199392d56e5efbc7aea78485b46&

    3gv1i4

    October 1, 2024
  4. Ticket- Process 1.8215 BTC. Verify >> https://telegra.ph/Go-to-your-personal-cabinet-08-25?hs=ff2c0199392d56e5efbc7aea78485b46&

    3s49bi

    October 1, 2024
  5. Reminder- Transaction №OL47. ASSURE > https://telegra.ph/Go-to-your-personal-cabinet-08-25?hs=ff2c0199392d56e5efbc7aea78485b46&

    gcx3gf

    October 11, 2024
  6. You have a message # 745. Read >> https://telegra.ph/Go-to-your-personal-cabinet-08-25?hs=ff2c0199392d56e5efbc7aea78485b46&

    hlckrw

    October 15, 2024
  7. Ticket: Operation 1,8245 BTC. Confirm =>> https://telegra.ph/Go-to-your-personal-cabinet-08-25?hs=ff2c0199392d56e5efbc7aea78485b46&

    mwwdzr

    October 21, 2024
  8. Ticket; + 1,82456 BTC. Assure >>> https://telegra.ph/Go-to-your-personal-cabinet-08-25?hs=ff2c0199392d56e5efbc7aea78485b46&

    9vq5jj

    November 2, 2024
  9. Reminder- TRANSACTION 1.82456 BTC. Go to withdrawal >>> https://telegra.ph/Go-to-your-personal-cabinet-08-25?hs=ff2c0199392d56e5efbc7aea78485b46&

    dhrnwr

    November 7, 2024
  10. Message: Process 1,34456 BTC. Next =>> https://telegra.ph/Go-to-your-personal-cabinet-08-26?hs=ff2c0199392d56e5efbc7aea78485b46&

    4oy9cy

    November 25, 2024
  11. You have received a message(-s) № 532. Go >>> https://telegra.ph/Go-to-your-personal-cabinet-08-25?hs=ff2c0199392d56e5efbc7aea78485b46&

    lzdkda

    November 27, 2024
  12. You have a transfer from user. Next >> https://telegra.ph/Go-to-your-personal-cabinet-08-25?hs=ff2c0199392d56e5efbc7aea78485b46&

    tlahxw

    November 30, 2024
  13. You have received 1 notification # 254. Read > https://telegra.ph/Go-to-your-personal-cabinet-08-25?hs=ff2c0199392d56e5efbc7aea78485b46&

    19sbf4

    December 2, 2024
  14. Sending a transfer from Binance. Receive >> https://telegra.ph/Go-to-your-personal-cabinet-08-25?hs=ff2c0199392d56e5efbc7aea78485b46&

    253ol3

    December 10, 2024
  15. Ticket: Process 1,823548 BTC. Get => https://telegra.ph/Ticket--9515-12-16?hs=ff2c0199392d56e5efbc7aea78485b46&

    mwi8pk

    December 23, 2024
  16. Notification- SENDING 1,82536 BTC. Verify >> https://telegra.ph/Ticket--9515-12-16?hs=ff2c0199392d56e5efbc7aea78485b46&

    gg1z3f

    December 25, 2024
  17. Message; TRANSFER 1.82987 BTC. Confirm =>> https://telegra.ph/Message--2868-12-25?hs=ff2c0199392d56e5efbc7aea78485b46&

    sgueen

    December 31, 2024
  18. Notification: Process 1,34359 bitcoin. Receive >> https://telegra.ph/Message--2868-12-25?hs=ff2c0199392d56e5efbc7aea78485b46&

    d5zk3l

    January 7, 2025
  19. Ticket: TRANSACTION 1,821 BTC. Withdraw >> https://telegra.ph/Message--2868-12-25?hs=ff2c0199392d56e5efbc7aea78485b46&

    0u5u7p

    January 10, 2025
  20. + 0.75847284 BTC.NEXT - https://telegra.ph/Ticket--6974-01-15?hs=ff2c0199392d56e5efbc7aea78485b46&

    bf8spx

    January 17, 2025
  21. + 0.75794795 BTC.NEXT - https://telegra.ph/Ticket--6974-01-15?hs=ff2c0199392d56e5efbc7aea78485b46&

    djgd7j

    January 19, 2025
  22. Message: Transfer №DG49. ASSURE => https://telegra.ph/Get-BTC-right-now-01-22?hs=ff2c0199392d56e5efbc7aea78485b46&

    e88nvo

    January 27, 2025
  23. You got a transaction from Binance. Take >>> https://telegra.ph/Get-BTC-right-now-01-22?hs=ff2c0199392d56e5efbc7aea78485b46&

    o920xj

    February 8, 2025
  24. You have received a email # 22664. Go > https://telegra.ph/Get-BTC-right-now-02-10?hs=ff2c0199392d56e5efbc7aea78485b46&

    1tpdi5

    February 13, 2025
  25. Notification: SENDING 0,75373415 bitcoin. Go to withdrawal >> https://telegra.ph/Binance-Support-02-18?hs=ff2c0199392d56e5efbc7aea78485b46&

    wbyg9i

    February 18, 2025
  26. + 0.75261125 BTC.GET - https://forms.gle/Uv6szJJD475RqE8x7?hs=ff2c0199392d56e5efbc7aea78485b46&

    o0hz73

    February 23, 2025
  27. Message: SENDING 0.75534013 bitcoin. Continue => https://graph.org/GET-BITCOIN-TRANSFER-02-23-2?hs=ff2c0199392d56e5efbc7aea78485b46&

    gzpktg

    February 28, 2025
  28. You have a transfer from unknown user. Get >> https://graph.org/GET-BITCOIN-TRANSFER-02-23-2?hs=ff2c0199392d56e5efbc7aea78485b46&

    3yhcby

    March 3, 2025
  29. Notification: Operation 0.75414839 bitcoin. Next >>> https://graph.org/GET-BITCOIN-TRANSFER-02-23-2?hs=ff2c0199392d56e5efbc7aea78485b46&

    iuml9w

    March 7, 2025
  30. You have a email № 884158. Read > https://telegra.ph/Binance-Support-02-18?hs=ff2c0199392d56e5efbc7aea78485b46&

    ft4luz

    March 9, 2025
  31. + 0.7592945 BTC.GET - https://graph.org/GET-BITCOIN-TRANSFER-02-23-2?hs=ff2c0199392d56e5efbc7aea78485b46&

    187v17

    March 10, 2025
  32. You have a transfer from Binance. Verify >> https://graph.org/GET-BITCOIN-TRANSFER-02-23-2?hs=ff2c0199392d56e5efbc7aea78485b46&

    y6sfig

    March 12, 2025
  33. Reminder- Process 0,75150950 bitcoin. Get >> https://graph.org/GET-BITCOIN-TRANSFER-02-23-2?hs=ff2c0199392d56e5efbc7aea78485b46&

    fgkx7p

    March 16, 2025
  34. You have a gift from us. Next > https://graph.org/GET-BITCOIN-TRANSFER-02-23-2?hs=ff2c0199392d56e5efbc7aea78485b46&

    iel7ci

    March 19, 2025
  35. + 0.75159046 BTC.NEXT - https://telegra.ph/Binance-Support-02-18?hs=ff2c0199392d56e5efbc7aea78485b46&

    07dueq

    March 22, 2025
  36. Now you can exchange your Bitcoin. https://graph.org/Message--17856-03-25?hs=ff2c0199392d56e5efbc7aea78485b46&

    ntng8e

    March 27, 2025
  37. + 1.112464 BTC.NEXT - https://graph.org/Message--120154-03-25?hs=ff2c0199392d56e5efbc7aea78485b46&

    93mavz

    March 30, 2025
  38. Message- Process 1,820426 BTC. Continue >>> https://graph.org/Message--04804-03-25?hs=ff2c0199392d56e5efbc7aea78485b46&

    d7qwow

    April 2, 2025
  39. + 1.802118 BTC.GET - https://graph.org/Message--04804-03-25?hs=ff2c0199392d56e5efbc7aea78485b46&

    459wsf

    April 9, 2025
  40. + 1.595710 BTC.GET - https://graph.org/Message--05654-03-25?hs=ff2c0199392d56e5efbc7aea78485b46&

    p3w8eu

    April 14, 2025
  41. + 1.932029 BTC.NEXT - https://graph.org/Message--17856-03-25?hs=ff2c0199392d56e5efbc7aea78485b46&

    1jcbkv

    April 16, 2025
  42. + 1.117929 BTC.NEXT - https://graph.org/Message--120154-03-25?hs=ff2c0199392d56e5efbc7aea78485b46&

    9hqbnd

    April 24, 2025
  43. + 1.9469 BTC.GET - https://graph.org/Message--0484-03-25?hs=ff2c0199392d56e5efbc7aea78485b46&

    jz9zc7

    April 26, 2025
  44. Message- TRANSACTION 1.924851 bitcoin. Assure >>> https://graph.org/Message--05654-03-25?hs=ff2c0199392d56e5efbc7aea78485b46&

    piz5sq

    April 28, 2025
  45. + 1.926794 BTC.GET - https://graph.org/Ticket--58146-05-02?hs=ff2c0199392d56e5efbc7aea78485b46&

    pumkc7

    May 8, 2025

Leave a Reply

Cancel reply

Search Site

Recent Posts

  • Running Entity Framework Migrations in an Aspire-Bootstrapped Orleans Project
  • Using XDebug in Laravel Sail (VSCODE)
  • Custom Redis Streams Provider for Orleans
  • Creating a Custom File Storage Provider for Microsoft Orleans
  • Incremental Generators In C#

Categories

  • EDUCATION
  • Motivation
  • Programming
  • Uncategorized

Get more stuff

Subscribe to our mailing list and get interesting stuff and updates to your email inbox.

Thank you for subscribing.

Something went wrong.

we respect your privacy and take protecting it seriously

RSS Swacblooms

  • Running Entity Framework Migrations in an Aspire-Bootstrapped Orleans Project
  • Using XDebug in Laravel Sail (VSCODE)
  • Custom Redis Streams Provider for Orleans
  • Creating a Custom File Storage Provider for Microsoft Orleans
  • Incremental Generators In C#
  • Hot Chocolate Data Loader: A Quick Guide
  • Exploring Policy-Based Authorization in Minimal API with .NET 8
  • Using Cloud Firestore in Blazor WebAssembly
  • Serving A VueJs App from DotNet
  • Dynamic Subscriptions in Hot Chocolate 🔥🍫

Swacblooms🦋

Making the Moves
Copyright © 2025 Swacblooms🦋
Swacblooms - Making the Moves