# ParsedSchemaCache - Dependency Injection Web API Guide
Complete guide for using ParsedSchemaCache with ASP.NET Core Web API and modern Program.cs (minimal hosting model).
## Quick Start
### 1. Install NuGet Package
```bash
dotnet add package JsonEvalRs
```
### 2. Create a New Web API Project
```bash
dotnet new webapi -n JsonEvalDemo
cd JsonEvalDemo
dotnet add package JsonEvalRs
```
## Complete Example: Program.cs (Minimal Hosting Model)
### Basic Setup with Global Cache
```csharp
using JsonEvalRs;
var builder = WebApplication.CreateBuilder(args);
// Add services
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// ✅ Add JsonEvalRs global cache
builder.Services.AddJsonEvalRsCache();
var app = builder.Build();
// Configure middleware
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
```
### Advanced Setup with Pre-loaded Schemas
```csharp
using JsonEvalRs;
using System.Text.Json;
var builder = WebApplication.CreateBuilder(args);
// Add services
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// ✅ Add JsonEvalRs cache with pre-loaded schemas
builder.Services.AddJsonEvalRsCache(cache =>
{
var logger = builder.Logging.Services.BuildServiceProvider()
.GetRequiredService<ILogger<Program>>();
try
{
// Load schemas from files
var schemasPath = Path.Combine(builder.Environment.ContentRootPath, "Schemas");
if (Directory.Exists(schemasPath))
{
foreach (var file in Directory.GetFiles(schemasPath, "*.json"))
{
var schemaName = Path.GetFileNameWithoutExtension(file);
var schemaJson = File.ReadAllText(file);
cache.Insert(schemaName, schemaJson);
logger.LogInformation("Loaded schema: {SchemaName}", schemaName);
}
logger.LogInformation("✅ Pre-loaded {Count} schemas", cache.Count);
}
else
{
logger.LogWarning("Schemas directory not found: {Path}", schemasPath);
}
}
catch (Exception ex)
{
logger.LogError(ex, "Failed to pre-load schemas");
}
});
var app = builder.Build();
// ✅ Initialize cache (triggers pre-loading)
app.UseJsonEvalRsCache();
// Configure middleware
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
```
## Controller Examples
### Basic Controller with Cache Injection
```csharp
using JsonEvalRs;
using Microsoft.AspNetCore.Mvc;
namespace JsonEvalDemo.Controllers;
[ApiController]
[Route("api/[controller]")]
public class EvaluationController : ControllerBase
{
private readonly ParsedSchemaCache _cache;
private readonly ILogger<EvaluationController> _logger;
public EvaluationController(
ParsedSchemaCache cache,
ILogger<EvaluationController> logger)
{
_cache = cache;
_logger = logger;
}
[HttpGet("cache/stats")]
public IActionResult GetCacheStats()
{
var stats = _cache.GetStats();
return Ok(new
{
entryCount = stats.EntryCount,
keys = stats.Keys,
isEmpty = _cache.IsEmpty
});
}
[HttpPost("evaluate")]
public IActionResult Evaluate([FromBody] EvaluationRequest request)
{
try
{
// Ensure schema is cached
if (!_cache.Contains(request.SchemaKey))
{
if (string.IsNullOrEmpty(request.Schema))
{
return BadRequest($"Schema '{request.SchemaKey}' not found in cache and no schema provided");
}
_cache.Insert(request.SchemaKey, request.Schema);
_logger.LogInformation("Cached new schema: {SchemaKey}", request.SchemaKey);
}
// Use the schema (benefits from cached ParsedSchema internally)
using var eval = new JSONEval(
request.Schema ?? GetSchemaFromCache(request.SchemaKey),
request.Context,
request.Data
);
eval.Evaluate(request.Data, request.Context);
var result = eval.GetEvaluatedSchema(skipLayout: false);
return Ok(new
{
success = true,
result = result,
schemaKey = request.SchemaKey,
cached = true
});
}
catch (JsonEvalException ex)
{
_logger.LogError(ex, "Evaluation failed");
return BadRequest(new { error = ex.Message });
}
}
[HttpPost("validate")]
public IActionResult Validate([FromBody] ValidationRequest request)
{
try
{
if (!_cache.Contains(request.SchemaKey))
{
return BadRequest($"Schema '{request.SchemaKey}' not found in cache");
}
var schema = GetSchemaFromCache(request.SchemaKey);
using var eval = new JSONEval(schema, request.Context, null);
var validationResult = eval.Validate(request.Data, request.Context, null);
return Ok(new
{
hasError = validationResult.HasError,
errors = validationResult.Errors.Select(e => new
{
path = e.Key,
ruleType = e.Value.RuleType,
message = e.Value.Message
})
});
}
catch (JsonEvalException ex)
{
_logger.LogError(ex, "Validation failed");
return BadRequest(new { error = ex.Message });
}
}
[HttpDelete("cache/{key}")]
public IActionResult RemoveFromCache(string key)
{
var removed = _cache.Remove(key);
if (removed)
{
_logger.LogInformation("Removed schema from cache: {Key}", key);
return Ok(new { message = $"Schema '{key}' removed from cache" });
}
return NotFound(new { message = $"Schema '{key}' not found in cache" });
}
[HttpDelete("cache")]
public IActionResult ClearCache()
{
var count = _cache.Count;
_cache.Clear();
_logger.LogInformation("Cleared cache ({Count} entries)", count);
return Ok(new { message = $"Cleared {count} schemas from cache" });
}
private string GetSchemaFromCache(string key)
{
// In real app, you'd store the schema JSON alongside the cached ParsedSchema
// For this example, we assume it's available
throw new NotImplementedException("Implement schema retrieval logic");
}
}
// Request models
public class EvaluationRequest
{
public string SchemaKey { get; set; } = "";
public string? Schema { get; set; }
public string Data { get; set; } = "{}";
public string? Context { get; set; }
}
public class ValidationRequest
{
public string SchemaKey { get; set; } = "";
public string Data { get; set; } = "{}";
public string? Context { get; set; }
}
```
### Advanced: Service Layer Pattern
```csharp
// ISchemaService.cs
public interface ISchemaService
{
Task<string> GetSchemaAsync(string key);
Task CacheSchemaAsync(string key, string schema);
bool IsCached(string key);
Task<object> EvaluateAsync(string schemaKey, string data, string? context = null);
}
// SchemaService.cs
public class SchemaService : ISchemaService
{
private readonly ParsedSchemaCache _cache;
private readonly ILogger<SchemaService> _logger;
private readonly Dictionary<string, string> _schemaStore; // Simple in-memory store
public SchemaService(
ParsedSchemaCache cache,
ILogger<SchemaService> logger)
{
_cache = cache;
_logger = logger;
_schemaStore = new Dictionary<string, string>();
}
public Task<string> GetSchemaAsync(string key)
{
if (_schemaStore.TryGetValue(key, out var schema))
{
return Task.FromResult(schema);
}
throw new KeyNotFoundException($"Schema '{key}' not found");
}
public Task CacheSchemaAsync(string key, string schema)
{
_cache.Insert(key, schema);
_schemaStore[key] = schema;
_logger.LogInformation("Schema cached: {Key}", key);
return Task.CompletedTask;
}
public bool IsCached(string key)
{
return _cache.Contains(key);
}
public async Task<object> EvaluateAsync(string schemaKey, string data, string? context = null)
{
var schema = await GetSchemaAsync(schemaKey);
using var eval = new JSONEval(schema, context, data);
eval.Evaluate(data, context);
return eval.GetEvaluatedSchema(skipLayout: false);
}
}
// Program.cs - Register service
builder.Services.AddJsonEvalRsCache();
builder.Services.AddScoped<ISchemaService, SchemaService>();
// Controller using service
[ApiController]
[Route("api/[controller]")]
public class SchemaController : ControllerBase
{
private readonly ISchemaService _schemaService;
public SchemaController(ISchemaService schemaService)
{
_schemaService = schemaService;
}
[HttpPost("evaluate")]
public async Task<IActionResult> Evaluate([FromBody] EvaluationRequest request)
{
try
{
if (!_schemaService.IsCached(request.SchemaKey) && !string.IsNullOrEmpty(request.Schema))
{
await _schemaService.CacheSchemaAsync(request.SchemaKey, request.Schema);
}
var result = await _schemaService.EvaluateAsync(
request.SchemaKey,
request.Data,
request.Context
);
return Ok(result);
}
catch (Exception ex)
{
return BadRequest(new { error = ex.Message });
}
}
}
```
## Configuration Patterns
### Pattern 1: Configuration-based Pre-loading
```csharp
// appsettings.json
{
"JsonEvalRs": {
"Schemas": {
"validation": "schemas/validation-schema.json",
"calculation": "schemas/calculation-schema.json",
"report": "schemas/report-schema.json"
}
}
}
// Program.cs
builder.Services.Configure<JsonEvalRsOptions>(
builder.Configuration.GetSection("JsonEvalRs"));
builder.Services.AddJsonEvalRsCache(cache =>
{
var options = builder.Configuration
.GetSection("JsonEvalRs:Schemas")
.Get<Dictionary<string, string>>();
if (options != null)
{
foreach (var (key, path) in options)
{
var fullPath = Path.Combine(builder.Environment.ContentRootPath, path);
if (File.Exists(fullPath))
{
var schema = File.ReadAllText(fullPath);
cache.Insert(key, schema);
}
}
}
});
```
### Pattern 2: Database-backed Schemas
```csharp
// Program.cs
builder.Services.AddJsonEvalRsCache(cache =>
{
var serviceProvider = builder.Services.BuildServiceProvider();
using var scope = serviceProvider.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
var schemas = dbContext.Schemas
.Where(s => s.IsActive)
.ToList();
foreach (var schema in schemas)
{
cache.Insert(schema.Key, schema.JsonContent);
}
});
```
### Pattern 3: Lazy Loading with Factory
```csharp
// SchemaFactory.cs
public class SchemaFactory
{
private readonly ParsedSchemaCache _cache;
private readonly IConfiguration _configuration;
public SchemaFactory(ParsedSchemaCache cache, IConfiguration configuration)
{
_cache = cache;
_configuration = configuration;
}
public string GetOrLoadSchema(string key)
{
if (!_cache.Contains(key))
{
var schemaPath = _configuration[$"Schemas:{key}"];
if (!string.IsNullOrEmpty(schemaPath))
{
var schema = File.ReadAllText(schemaPath);
_cache.Insert(key, schema);
}
}
// Return schema from your storage
return LoadSchemaFromStorage(key);
}
private string LoadSchemaFromStorage(string key)
{
// Implement your storage logic
throw new NotImplementedException();
}
}
// Program.cs
builder.Services.AddJsonEvalRsCache();
builder.Services.AddSingleton<SchemaFactory>();
```
## Testing
### Unit Test Example
```csharp
using Xunit;
using JsonEvalRs;
public class SchemaServiceTests
{
[Fact]
public void CacheService_ShouldStoreAndRetrieve()
{
// Arrange
using var cache = new ParsedSchemaCache();
var schemaJson = @"{
""type"": ""object"",
""properties"": {
""name"": { ""type"": ""string"" }
}
}";
// Act
cache.Insert("test-schema", schemaJson);
// Assert
Assert.True(cache.Contains("test-schema"));
Assert.Equal(1, cache.Count);
}
[Fact]
public void GlobalCache_ShouldBeShared()
{
// Arrange & Act
ParsedSchemaCache.Global.Insert("shared-schema", "{}");
// Assert
Assert.True(ParsedSchemaCache.Global.Contains("shared-schema"));
}
}
```
### Integration Test Example
```csharp
public class EvaluationControllerTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly HttpClient _client;
public EvaluationControllerTests(WebApplicationFactory<Program> factory)
{
_client = factory.CreateClient();
}
[Fact]
public async Task CacheStats_ShouldReturnStats()
{
// Act
var response = await _client.GetAsync("/api/evaluation/cache/stats");
// Assert
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
Assert.Contains("entryCount", content);
}
}
```
## Project Structure
```
JsonEvalDemo/
├── Program.cs # Minimal hosting setup
├── appsettings.json # Configuration
├── Controllers/
│ ├── EvaluationController.cs
│ └── SchemaController.cs
├── Services/
│ ├── ISchemaService.cs
│ └── SchemaService.cs
├── Models/
│ ├── EvaluationRequest.cs
│ └── ValidationRequest.cs
└── Schemas/ # Pre-loaded schema files
├── validation-schema.json
├── calculation-schema.json
└── report-schema.json
```
## Best Practices
1. **Use Global Cache for Shared Schemas**
- One cache instance across entire application
- Schemas shared between all requests
2. **Pre-load on Startup**
- Load frequently-used schemas during app initialization
- Reduces first-request latency
3. **Monitor Cache Usage**
- Expose cache stats via health check endpoint
- Log cache operations for debugging
4. **Handle Cache Misses Gracefully**
- Fall back to loading schema if not cached
- Cache automatically on first use
5. **Clear Cache Strategically**
- Only clear when schemas are updated
- Consider selective removal instead of full clear
6. **Use Scoped Services**
- Schema service as scoped for per-request isolation
- Cache as singleton for shared state
## Performance Metrics
With ParsedSchemaCache:
- ✅ **~100x faster** schema initialization (after first parse)
- ✅ **~10x less memory** (Arc-based sharing)
- ✅ **Zero parsing overhead** for cached schemas
- ✅ **Thread-safe** concurrent access
## Troubleshooting
### Schema Not Found
```csharp
if (!cache.Contains("my-schema"))
{
var keys = cache.GetKeys();
_logger.LogWarning("Schema not found. Available: {Keys}", string.Join(", ", keys));
}
```
### Cache Not Initializing
Ensure `UseJsonEvalRsCache()` is called in Program.cs:
```csharp
var app = builder.Build();
app.UseJsonEvalRsCache(); // ← Required for pre-loading
```
### Native Library Not Found
Ensure the JsonEvalRs NuGet package includes native libraries for your platform.
## Summary
ParsedSchemaCache provides:
- ✅ **Simple DI integration** via `AddJsonEvalRsCache()`
- ✅ **Automatic pre-loading** on startup
- ✅ **Thread-safe** global or local instances
- ✅ **Performance** optimized for high-throughput scenarios
- ✅ **Flexible** configuration and usage patterns
Perfect for microservices, APIs, and high-performance applications! 🚀