JsonStores 0.4.1

dotnet add package JsonStores --version 0.4.1
                    
NuGet\Install-Package JsonStores -Version 0.4.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="JsonStores" Version="0.4.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="JsonStores" Version="0.4.1" />
                    
Directory.Packages.props
<PackageReference Include="JsonStores" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add JsonStores --version 0.4.1
                    
#r "nuget: JsonStores, 0.4.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package JsonStores@0.4.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=JsonStores&version=0.4.1
                    
Install as a Cake Addin
#tool nuget:?package=JsonStores&version=0.4.1
                    
Install as a Cake Tool

JsonStores

GitHub Nuget Nuget GitHub Workflow Status

Persist your data on JSON files in an easy and flexible way

  • Create stores (containing a single object) or repositories (containing a collection of items)
  • Thread-safe overloads (see docs)
  • Developed on top of System.Text.Json and compatible with netstandard2.1

Getting started

Install the package from Nuget

    dotnet add package JsonStores

Just add it to your DI container using AddJsonStores() extension method

    public void ConfigureServices(IServiceCollection services)
    {
        // add your dependencies

        services.AddJsonStores();
    }

Inject this with IJsonStore<T> or IJsonRepository<T, TKey> interfaces

    [ApiController]
    [Route("[controller]")]
    public class MyController : ControllerBase
    {
        private readonly IJsonRepository<MyClass, MyClassKey> _repository;
        private readonly IJsonStore<MyConfig> _config;

        public MyController(IJsonRepository<MyClass, MyClassKey> repository, IJsonStore<MyConfig> config)
        {
            _repository = repository;
            _config = config;
        }

        [HttpGet]
        public async Task<ActionResult> Get()
        {
            var items = await _repository.GetAllAsync();
            return Ok(items);
        }

        [HttpPost("ChangeData")]
        public async Task<ActionResult> Post(Dto dto)
        {
            var item = await _config.ReadOrCreateAsync();

            item.Data1 = dto.Data1;
            item.Data2 = dto.Data2;

            await _config.SaveAsync(item);
            return Ok();
        }
    }

Change options

Use the class JsonStoreOptions to customize

    public void ConfigureServices(IServiceCollection services)
    {
        var options = new JsonStoreOptions
        {
            // file location - default is the current directory
            Location = @"C:\my files",
            // how generate the file name - default is the name of the generic class
            NamingStrategy = new StaticNamingStrategy("myFile"),
            // file extension - default is 'json'
            FileExtension = "arq"
            // looks file writing time to avoid data loss - default is 'true'
            ThrowOnSavingChangedFile = false;
        };
        
        services.AddJsonRepository<MyClass, MyClassId>(options);
        services.AddJsonStore<Settings>(options => options.NamingStrategy = new StaticNamingStrategy("configs"));

        // you can add generic classes with other options
        services.AddJsonStores(options => options.Location = @"D:\stores", ServiceLifetime.Transient);
    }

Create your JSON repository

In order to instantiate the repository, you must specify a property as an unique key. The second generic parameter must match the type of this property.

  • If your class has an Id property, it will be used. You can change this behavior by adding the IgnoreIdProperty annotation to your class.
  • To use another property, add the JsonRepositoryKey annotation on any property of your class.

If your are manually instantiating your class, you can pass an expression in the constructor:

    var options = new JsonStoreOptions();
    var repository = new JsonRepository<MyClass, int>(options, myClass => myClass.Key);

An InvalidJsonRepositoryKeyException will be thrown if:

  • Your class don't have an Id or any property with the JsonRepositoryKey annotation.
  • There is more than one property with the JsonRepositoryKey annotation.
  • The property type don't match the second generic parameter.

Personalize your store using inheritance

You can also customize the behavior of your stores just extending JsonStore and JsonRepository classes.

    public class CustomStore : JsonRepository<MyClass, Guid>
    {
        protected override string FileName => "my-items";
    
        public CustomStore(options) : base(options, obj => obj.ObjId)
        {
        }

        // ...
    }

See the NoteStore class on sample app for details.

Concurrent stores

The Concurrent namespace contains the thread-safe overloads: ConcurrentJsonStore and ConcurrentJsonRepository.

Semaphore factories

In addition to the default parameters, you'll need to provide a ISemaphoreFactory. A binary Semaphore is obtained based on the type of the store (the T parameter) and used to control who can access the store.

There are three built-in implementations:

  • LocalSemaphoreFactory returns the same (singleton) semaphore. This lightweight instance may not work properly on a multi-instance app.
  • NamedSemaphoreFactory returns a named (system-wide) semaphore with the given string. This will be available for the entire SO.
  • PerFileSemaphoreFactory returns a named semaphore based on a given IJsonStoreOptions.

Named semaphores are only available on Windows machines. See this issue for details.

Custom factory

To implement your own factory, just return a binary (from 1 to 1) Semaphore object.

    public class CustomFactory : ISemaphoreFactory
    {
        public Semaphore GetSemaphore<T>()
        {
            if (typeof(T) == typeof(string))
                return new Semaphore(1, 1, "my-string-semaphore");
            if (typeof(T) == typeof(int))
                return new Semaphore(1, 1, "my-int-semaphore");

            throw new ApplicationException($"Invalid type: '{typeof(T).Name}'.");
        }
    }

Additional repository methods

ConcurrentJsonRepository class contains methods to edit and save the store as a single operation. If you are implementing a multi-instance app (or using multiple repositories instances), use there methods to avoid a FileChangedException.

Sample app

You can see the usage of both JsonStore and JsonRepository with the CLI app on JsonStores.Sample project.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.4.1 481 7/7/2021
0.3.2 394 4/15/2021
0.3.1 412 3/26/2021
0.3.0 403 3/25/2021
0.2.1 414 3/22/2021
0.2.0 683 3/20/2021
0.1.1 461 3/19/2021
0.1.0 652 3/19/2021

Thread-safe stores