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
| Operation | HTTP method | Request URL |
|---|---|---|
| Get Page | GET | https://school-api.example.com/courses?offset={offset}&limit={limit} |
| Get | GET | https://school-api.example.com/courses/{id} |
| Add | POST | https://school-api.example.com/courses |
| Update | PUT | https://school-api.example.com/courses/{id} |
| Remove | DELETE | https://school-api.example.com/courses/{id} |
{
"Id": 1,
"Title": "Introduction to Biology"
}
Students Set
| Operation | HTTP method | Request URL |
|---|---|---|
| Get | GET | https://school-api.example.com/students/{id} |
| Get Page | GET | https://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;
});
});
AddFluxregisters the Flux Context in the DI container.AddService("school-api")registers a Flux Service namedschool-apiin the DI container.UsingJsonconfigures the Service to use Flux.JSON.AddSet<Course, int>registers a set for the Course resourse.FromJsonprovides the initial Set data from a JSON array.WithKeyselects the identifier of the model.AddSet<Student, int>registers a set for the Student resourse.EnrichQueryfilters read data based on the current operation and its parameters:- For the Get operation, the enriched query replaces the normal key lookup, so the
GetOperationDescriptorbranch filters data by the requested identifier. - For the Get Page operation,
EnrichQueryuses thecourseIdparameter to select only Students enrolled in the requested Course. Flux applies pagination after filtering the data.
- For the Get operation, the enriched query replaces the normal key lookup, so the
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
JsonSerializerOptionsAPI reference for the available options.