Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

When your application uses Flux to communicate with an external API, you can use Flux.JSON as an in-memory substitute for testing. Flux.JSON initializes Flux Set data from JSON strings or files, allowing the same application code to run without sending any real HTTP requests to the API.

Installation

Install the Flux.JSON NuGet package:

dotnet add package BitzArt.Flux.Json

Get Started

Consider the following example Web API that manages courses and students. In Flux.JSON, each resource is represented by an abstraction called Set.

Example Web API

Courses Set

OperationHTTP methodRequest URL
Get PageGEThttps://school-api.example.com/courses?offset={offset}&limit={limit}
GetGEThttps://school-api.example.com/courses/{id}
AddPOSThttps://school-api.example.com/courses
UpdatePUThttps://school-api.example.com/courses/{id}
RemoveDELETEhttps://school-api.example.com/courses/{id}
{
  "Id": 1,
  "Title": "Introduction to Biology"
}

Students Set

OperationHTTP methodRequest URL
GetGEThttps://school-api.example.com/students/{id}
Get PageGEThttps://school-api.example.com/courses/{courseId}/students?offset={offset}&limit={limit}
{
  "Id": 1,
  "CourseIds": [1, 2],
  "Name": "Alice"
}

Note

Notice that Students can be fetched by ID of a Course they participate in.

Configuration

Add respective model classes to match the API’s resources:

public class Course
{
    public int? Id { get; set; }
    public string? Title { get; set; }
}
public class Student
{
    public int? Id { get; set; }
    public List<int>? CourseIds { get; set; }
    public string? Name { get; set; }
}

Add the initial course and student data as JSON arrays:

var coursesJson =
"""
[
  {
    "Id": 1,
    "Title": "Introduction to Biology"
  },
  {
    "Id": 2,
    "Title": "Advanced Mathematics"
  }
]
""";
var studentsJson =
"""
[
  {
    "Id": 1,
    "CourseIds": [1],
    "Name": "Alice"
  },
  {
    "Id": 2,
    "CourseIds": [1, 2],
    "Name": "Ben"
  },
  {
    "Id": 3,
    "CourseIds": [2],
    "Name": "Carla"
  }
]
""";

Configure Flux and the Course and Student Sets:

services.AddFlux(flux =>
{
    flux.AddService("school-api")
        .UsingJson()
        .AddSet<Course>()
            .FromJson(coursesJson)
            .WithKey(course => course.Id)
        .AddSet<Student>()
            .FromJson(studentsJson)
            .WithKey(student => student.Id)
            .EnrichQuery((query, operation) =>
            {
                if (operation is GetOperationDescriptor getOperation)
                {
                    return query.Where(student => student.Id == (int?)getOperation.Id);
                }

                if (operation is GetPageOperationDescriptor &&
                    operation.Parameters is INamedOperationParameterCollection parameters)
                {
                    var courseId = (int)parameters.Values.Single(parameter => parameter.Key == "courseId").Value;

                    return query.Where(student => student.CourseIds!.Contains(courseId));
                }

                return query;
            });
});
  • AddFlux registers the Flux Context in the DI container.
  • AddService("school-api") registers a Flux Service named school-api in the DI container.
  • UsingJson configures the Service to use Flux.JSON.
  • AddSet<Course, int> registers a set for the Course resourse.
  • FromJson provides the initial Set data from a JSON array.
  • WithKey selects the identifier of the model.
  • AddSet<Student, int> registers a set for the Student resourse.
  • EnrichQuery filters read data based on the current operation and its parameters:
    • For the Get operation, the enriched query replaces the normal key lookup, so the GetOperationDescriptor branch filters data by the requested identifier.
    • For the Get Page operation, EnrichQuery uses the courseId parameter to select only Students enrolled in the requested Course. Flux applies pagination after filtering the data.

Load Data from Files

Use FromJsonFile when the initial data is stored in files. Pass a base directory to UsingJson and a file name to FromJsonFile. The following configuration loads Course data from data/courses.json:

services.AddFlux(flux =>
{
    flux.AddService("school-data")
        .UsingJson("data")
        .AddSet<Course>()
            .FromJsonFile("courses.json")
            .WithKey(course => course.Id);
});

Resolve Services

Inject Set Contexts to work with set data:

public class SchoolService(
    IFluxSetContext<Course> courseSetContext,
    IFluxSetContext<Student> studentSetContext)
{
}

Work with Sets

Courses

Use the injected courseSetContext to run Course operations:

var coursesPage = await courseSetContext.GetPageAsync(offset: 0, limit: 20);

var course = await courseSetContext.GetAsync(courseId);

await courseSetContext.AddAsync(newCourse);

await courseSetContext.RemoveAsync(courseId);

Note

Add, update, and remove operations change the in-memory Course data but do not modify coursesJson. Flux.JSON does not support partial updates.

Students

The Student Get Page path contains {courseId}. Pass its value as a named operation parameter when you call studentSetContext.GetPageAsync:

var parameters = new OperationParameterCollection(
    new List<KeyValuePair<string, object>>
    {
        new("courseId", 1)
    });

var studentsPage = await studentSetContext.GetPageAsync(
    offset: 0,
    limit: 20,
    parameters: parameters);

The resulting page contains Alice and Ben, who are enrolled in Course 1.

JSON Serializer Configuration

Flux.JSON uses System.Text.Json to serialize and deserialize JSON data.

Each Flux Service registered with UsingJson has its own JsonSerializerOptions. Use ConfigureJsonSerializer to configure those options:

services.AddFlux(flux =>
{
    flux.AddService("example-data")
        .UsingJson()
        .ConfigureJsonSerializer((JsonSerializerOptions options) =>
        {
            // Configure the JsonSerializerOptions here
        });
});

The configuration applies to the initial data loaded for every Set belonging to the "example-data" Flux Service.

Tip

See the JsonSerializerOptions API reference for the available options.