A .NET library that provides declarative, component-based reporting capabilities for Blazor applications connected to Snowflake. Build interactive data visualizations with minimal code.
# Add the Core library for services
dotnet add package SnowflakeProxy.Core
# Add the Blazor library for components
dotnet add package SnowflakeProxy.Blazor1. Configure your Snowflake connection using environment variables:
Copy the example environment file and configure your credentials:
cp example.env .env
# Edit .env with your Snowflake account detailsRun the setup script to configure user secrets:
./tools/setup-secrets.shThis securely stores credentials using .NET user secrets (not in source control). See example.env for all available configuration options.
You can also use user-secrets directly.
2. Register services in Program.cs:
using SnowflakeProxy.Core.Services;
using SnowflakeProxy.Core.Models;
var builder = WebApplication.CreateBuilder(args);
// Configure Snowflake connection
builder.Services.Configure<SnowflakeConfiguration>(
builder.Configuration.GetSection("Snowflake"));
// Register services
builder.Services.AddSingleton<ISnowflakeService, DirectSnowflakeService>();
builder.Services.AddSingleton<ICacheService, MemoryCacheService>();
builder.Services.AddSingleton<IVisualizationRenderer, VegaLiteRenderer>();
builder.Services.AddScoped<IReportService, DirectReportService>();
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
var app = builder.Build();
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode();
app.Run();3. Import the namespace in _Imports.razor:
@using SnowflakeProxy.Blazor.Components4. Use the component in your pages:
@page "/reports"
@rendermode InteractiveServer
<h1>Sales Report</h1>
<ChartComponent
Query="SELECT region, SUM(revenue) as total FROM sales GROUP BY region"
ChartType="bar"
ShowMetadata="true"
CacheTtl="TimeSpan.FromMinutes(15)" />The ChartComponent is a low-level primitive for executing inline SQL queries and rendering visualizations.
<ChartComponent
Query="SELECT * FROM INFORMATION_SCHEMA.TABLES LIMIT 10"
ChartType="table"
ShowMetadata="true" /><ChartComponent
Query="SELECT category, SUM(amount) as total FROM sales GROUP BY category"
ChartType="bar" /><ChartComponent
Query="SELECT date, revenue FROM daily_sales ORDER BY date"
ChartType="line" /><ChartComponent
Query="SELECT * FROM data"
Spec="@(new {
mark = "circle",
encoding = new {
x = new { field = "x_value", type = "quantitative" },
y = new { field = "y_value", type = "quantitative" },
size = new { field = "size_value", type = "quantitative" }
}
})" />Create custom components that wrap ChartComponent for domain-specific reports:
EnrollmentStepsTable.razor:
@using SnowflakeProxy.Core.Services
@using SnowflakeProxy.Core.Models
<ChartComponent
Query="@QueryString"
ChartType="table"
ShowMetadata="@ShowMetadata"
CacheTtl="@CacheTtl" />
@code {
/// <summary>
/// Number of enrollment steps to display
/// </summary>
[Parameter] public int Limit { get; set; } = 10;
/// <summary>
/// Show metadata (row count, cache status)
/// </summary>
[Parameter] public bool ShowMetadata { get; set; } = true;
/// <summary>
/// Cache TTL
/// </summary>
[Parameter] public TimeSpan? CacheTtl { get; set; } = TimeSpan.FromMinutes(10);
private string QueryString => $"SELECT * FROM ENROLLMENTS LIMIT {Limit}";
}Usage:
<EnrollmentStepsTable Limit="20" ShowMetadata="false" />| Parameter | Type | Default | Description |
|---|---|---|---|
Query |
string |
"" |
SQL query to execute against Snowflake |
ChartType |
string |
"bar" |
Type of visualization: "table", "bar", "line", "area", "pie" |
Spec |
object? |
null |
Custom VegaLite specification (overrides ChartType) |
ShowMetadata |
bool |
false |
Show row count and cache status below the chart |
CacheTtl |
TimeSpan? |
null |
Cache duration (e.g., TimeSpan.FromMinutes(10)) |
Width |
int |
800 |
Chart width in pixels |
Height |
int |
400 |
Chart height in pixels |
Title |
string? |
null |
Chart title |
XAxisLabel |
string? |
null |
X-axis label |
YAxisLabel |
string? |
null |
Y-axis label |
Handles connections and query execution against Snowflake.
public interface ISnowflakeService
{
Task<DataTable> ExecuteQueryAsync(string query, CancellationToken cancellationToken = default);
}Implementations:
DirectSnowflakeService- Direct connection to Snowflake (Phase 1)MockSnowflakeService- Mock service for testing
Orchestrates report generation with caching and visualization.
public interface IReportService
{
Task<ReportResult> GenerateReportAsync(
ReportConfig config,
CancellationToken cancellationToken = default);
}Implementations:
DirectReportService- Synchronous report generation (Phase 1)
Transforms data into visual representations.
public interface IVisualizationRenderer
{
Task<string> RenderAsync(
DataTable data,
VisualizationConfig config,
CancellationToken cancellationToken = default);
}Implementations:
VegaLiteRenderer- Generates VegaLite specifications for interactive charts
Provides caching for query results.
public interface ICacheService
{
Task<T?> GetAsync<T>(string key, CancellationToken cancellationToken = default);
Task SetAsync<T>(string key, T value, TimeSpan? ttl = null, CancellationToken cancellationToken = default);
}Implementations:
MemoryCacheService- In-memory caching with TTL support
graph TD
Root[SnowflakeProxy/]
Root --> Core[SnowflakeProxy.Core/<br/>Framework-agnostic business logic]
Core --> CoreModels[Models/]
Core --> CoreServices[Services/]
Core --> CoreExtensions[Extensions/]
CoreModels --> ReportConfig[ReportConfig.cs]
CoreModels --> VisualizationConfig[VisualizationConfig.cs]
CoreModels --> ReportResult[ReportResult.cs]
CoreModels --> SnowflakeConfig[SnowflakeConfiguration.cs]
CoreServices --> ISnowflakeService[ISnowflakeService.cs]
CoreServices --> IReportService[IReportService.cs]
CoreServices --> IVisualizationRenderer[IVisualizationRenderer.cs]
CoreServices --> ICacheService[ICacheService.cs]
CoreServices --> DirectSnowflake[DirectSnowflakeService.cs]
CoreServices --> DirectReport[DirectReportService.cs]
CoreServices --> VegaLite[VegaLiteRenderer.cs]
CoreServices --> MemoryCache[MemoryCacheService.cs]
CoreServices --> MockSnowflake[MockSnowflakeService.cs]
CoreExtensions --> DataTableExt[DataTableExtensions.cs]
Root --> Blazor[SnowflakeProxy.Blazor/<br/>Blazor components]
Blazor --> Components[Components/]
Blazor --> Wwwroot[wwwroot/]
Components --> ChartComponent[ChartComponent.razor]
Components --> EnrollmentTable[EnrollmentStepsTable.razor]
Wwwroot --> SnowflakeJS[snowflake-reporting.js]
Root --> Sample[SnowflakeProxy.Sample.Server/<br/>Sample Blazor Server app]
Sample --> SampleComponents[Components/]
Sample --> ProgramCS[Program.cs]
SampleComponents --> Pages[Pages/]
SampleComponents --> Layout[Layout/]
Pages --> Reports[Reports.razor]
Pages --> CustomReports[CustomReports.razor]
Root --> Tests[SnowflakeProxy.Core.Tests/<br/>Unit tests]
- Core interfaces and abstractions
- Direct Snowflake connectivity
- Basic Blazor components (
ChartComponent) - VegaLite visualization pipeline
- In-memory caching
- Sample Blazor Server application
- Role-based access control
- RESTful API endpoints
- HTTP client implementations
- Blazor WebAssembly support
- Distributed caching (Redis)
- Python visualization renderer (matplotlib, plotly, seaborn)
- Static site generation support
- Multi-tier caching
- Background report pre-computation
- Parquet serialization for efficient data transfer
- Visual report designer UI
- Audit logging and monitoring
- Plugin architecture
- Additional data source support
graph TD
A[ChartComponent<br/>Low-level primitive] --> B[Domain Components<br/>Reusable business components]
B --> C[Pages<br/>Application-specific pages]
ChartComponent: Accepts inline SQL queries and renders visualizations. Use this for ad-hoc analysis and quick reports.
Domain Components: Wrap ChartComponent with domain-specific logic, parameter validation, and default settings. Use this for reusable, parameterized reports.
flowchart TD
A[Page/Component] --> B[ChartComponent]
B --> C[IReportService]
C --> D{ICacheService}
D -->|Cache Miss| E[ISnowflakeService]
E -->|Execute Query| F[Query Results]
D -->|Cache Hit| F
F --> G[IVisualizationRenderer]
G --> H[VegaLite HTML/JS]
H --> I[Browser Rendering]
- Default: No caching
- Enabled: Set
CacheTtlparameter - Cache Key: Generated from query + parameters
- Implementation: In-memory (Phase 1), Redis (Phase 2+)
<!-- Cache for 10 minutes -->
<ChartComponent
Query="SELECT * FROM sales"
CacheTtl="TimeSpan.FromMinutes(10)" />- Use LIMIT: Constrain result sets for large tables
- Aggregate Early: Perform aggregations in SQL, not in memory
- Index Aware: Structure queries to use Snowflake's clustering keys
- Warehouse Sizing: Use appropriate warehouse size for query complexity
Use MockSnowflakeService for unit testing without Snowflake connection:
// In your test setup
services.AddSingleton<ISnowflakeService>(sp =>
{
var mockService = new MockSnowflakeService();
mockService.AddMockData("SELECT * FROM test", CreateTestDataTable());
return mockService;
});cd SnowflakeProxy.Core.Tests
dotnet testSee the SnowflakeProxy.Sample.Server project for complete working examples:
/reports: Ad-hoc queries usingChartComponent/custom-reports: Reusable domain components
Run the sample app:
cd SnowflakeProxy.Sample.Server
dotnet runNavigate to https://localhost:5001 (or the URL shown in the console).