OneSchema.AspNetCore 0.4.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package OneSchema.AspNetCore --version 0.4.0
                    
NuGet\Install-Package OneSchema.AspNetCore -Version 0.4.0
                    
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="OneSchema.AspNetCore" Version="0.4.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="OneSchema.AspNetCore" Version="0.4.0" />
                    
Directory.Packages.props
<PackageReference Include="OneSchema.AspNetCore" />
                    
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 OneSchema.AspNetCore --version 0.4.0
                    
#r "nuget: OneSchema.AspNetCore, 0.4.0"
                    
#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 OneSchema.AspNetCore@0.4.0
                    
#: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=OneSchema.AspNetCore&version=0.4.0
                    
Install as a Cake Addin
#tool nuget:?package=OneSchema.AspNetCore&version=0.4.0
                    
Install as a Cake Tool

OneSchema.AspNetCore

NuGet

ASP.NET Core integration for OneSchema validation webhooks. Provides minimal-API endpoint mapping, strongly-typed row models, optional JWT request validation, helpers for batch lookups against your data store, and an API client for retrieving imported rows.

Requires .NET 10+

Install

dotnet add package OneSchema.AspNetCore

Quick Start

The minimal setup is outlined below:

using OneSchema.AspNetCore.Authentication;
using OneSchema.AspNetCore.Validation;

var builder = WebApplication.CreateBuilder(args);

// 1. Register your handlers
builder.Services.AddValidationHookHandler<ContactValidationHandler, ContactRow>();
builder.Services.AddValidationHookHandler<ProductUniquenessHandler, ProductRow>();

// 2. (Optional) Configure JWT validation
builder.Services.AddOneSchemaJwtValidation(opts =>
{
    opts.ClientId = builder.Configuration["OneSchema:ClientId"]!;
    opts.ClientSecret = builder.Configuration["OneSchema:ClientSecret"]!;
});

var app = builder.Build();

// 3. Map endpoints (one per handler)
app.MapValidationHook<ContactValidationHandler, ContactRow>("/webhooks/validate-contacts")
    .RequireOneSchemaJwtValidation();

app.MapValidationHook<ProductUniquenessHandler, ProductRow>("/webhooks/validate-products");

// 4. Generate a JWT to send to OneSchema (optional, required if using JWT validation).
app.MapGet("/validation/jwt", (OneSchemaJwtContext jwtContext, HttpContext httpContext) =>
{    
    var userId = httpContext.User.FindFirstValue(ClaimTypes.NameIdentifier)!;

    var additionalClaims = new Dictionary<string, object>()
        { { "org-id", httpContext.User.FindFirstValue("org-id")! } };
    
    var jwt = jwtContext.GenerateEmbedToken(userId, additionalClaims);

    return jwt;
});

app.Run();

Each handler is bound to its own URL. Configure that URL in the OneSchema dashboard as a validation hook for the matching template.

Handlers

Handlers come in both dynamic and strongly typed flavors: BatchValidationHookHandler exposes each row's values as a dictionary, while BatchValidationHookHandler<TRow> maps them directly to the template columns. You'll receive every row in the webhook batch and a ValidationResultBuilder to attach errors/warnings against specific rows.

public class ContactValidationHandler : BatchValidationHookHandler<ContactRow>
{
    protected override Task ValidateAsync(
        ValidationHookRequest<ContactRow> request,
        ValidationResultBuilder results,
        CancellationToken cancellationToken)
    {
        foreach (var row in request.Rows)
        {
            if (string.IsNullOrWhiteSpace(row.Values.Email))
                results.ForRow(row).Error("email", "Email is required.");
            else if (!row.Values.Email.Contains('@'))
                results.ForRow(row).Warning("email", "Email appears invalid.")
                    .WithSuggestion("user@example.com");
        }

        return Task.CompletedTask;
    }
}

Because the whole batch is available, checks that look across rows or query an external system (such as a DB) need no extra machinery:

public class ProductUniquenessHandler : BatchValidationHookHandler<ProductRow>
{
    protected override Task ValidateAsync(
        ValidationHookRequest<ProductRow> request,
        ValidationResultBuilder results,
        CancellationToken cancellationToken)
    {
        var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
        foreach (var row in request.Rows)
        {
            if (string.IsNullOrWhiteSpace(row.Values.Sku))
            {
                results.ForRow(row).Error("sku", "SKU is required.");
                continue;
            }
            if (!seen.Add(row.Values.Sku))
                results.ForRow(row).Error("sku", "Duplicate SKU.");
        }
        return Task.CompletedTask;
    }
}

Defining a Row Model

To define a strongly typed model, map your OneSchema template columns to a POCO using JsonPropertyName:

public class ContactRow
{
    [JsonPropertyName("first_name")] public string? FirstName { get; set; }
    [JsonPropertyName("last_name")]  public string? LastName  { get; set; }
    [JsonPropertyName("email")]      public string? Email     { get; set; }
    [JsonPropertyName("phone")]      public string? Phone     { get; set; }
}

Database Lookups - RowIndex helper

For bulk checks involving database queries, use IndexBy to build a key → rows index, issue a single batch query, then pass the matching values back to ErrorForMatches (or WarningForMatches).

public class ContactEmailExistsHandler : BatchValidationHookHandler<ContactRow>
{
    protected override async Task ValidateAsync(
        ValidationHookRequest<ContactRow> request,
        ValidationResultBuilder results,
        CancellationToken cancellationToken)
    {
        var byEmail = request.Rows
            .Where(r => !string.IsNullOrWhiteSpace(r.Values.Email))
            .IndexBy(v => v.Email!, StringComparer.OrdinalIgnoreCase);

        var taken = await db.Users
            .Where(u => byEmail.Keys.Contains(u.Email))
            .Select(u => u.Email)
            .ToListAsync(cancellationToken);

        byEmail.ErrorForMatches(
            taken,
            results,
            column: "email",
            messageFactory: email => $"'{email}' is already registered.");
    }
}

JWT Validation

OneSchema signs webhook requests with an embed_user_jwt field in the request body. If present, the JWT claims are available on the webhook request via context.Request.Identity.

To enforce JWT validation:

  1. Register your configuration with AddOneSchemaJwtValidation.
  2. Chain .RequireOneSchemaJwtValidation() on each endpoint that should enforce it.
builder.Services.AddOneSchemaJwtValidation(opts =>
{
    opts.ClientId = builder.Configuration["OneSchema:ClientId"]!;
    opts.ClientSecret = builder.Configuration["OneSchema:ClientSecret"]!;
});

app.MapValidationHook<ContactValidationHandler, ContactRow>("/webhooks/validate-contacts")
    .RequireOneSchemaJwtValidation();
  1. Generate a JWT to use in your frontend via OneSchemaJwtContext.
app.MapGet("/validation/jwt", (OneSchemaJwtContext ctx, HttpContext httpContext) =>
{
    var userId = httpContext.User.FindFirstValue(ClaimTypes.NameIdentifier)!;

    var additionalClaims = new Dictionary<string, object>()
        { { "org-id", httpContext.User.FindFirstValue("org-id")! } };
    
    return ctx.GenerateEmbedToken(userId, additionalClaims);
});

Endpoints without RequireOneSchemaJwtValidation() accept unsigned requests. Invalid or missing tokens produce 401 Unauthorized.

OneSchema API Client

The library includes a typed client for the OneSchema REST API, currently wrapping the Get Imported Rows endpoint. Register it with your API key from the OneSchema dashboard:

using OneSchema.AspNetCore.Client;

builder.Services.AddOneSchemaApiClient(opts =>
{
    opts.ApiKey = builder.Configuration["OneSchema:ApiKey"]!;
    // Defaults to the US region; EU and Canada are also available:
    // opts.BaseAddress = OneSchemaApiOptions.EuropeBaseAddress;
});

Inject IOneSchemaApiClient to fetch imported rows for an embed session. Records can be mapped to the same POCO row models used by validation hooks, or accessed as dictionaries:

app.MapGet("/imports/{embedId:int}/rows", async (int embedId, IOneSchemaApiClient client) =>
{
    // Strongly typed page fetch
    var page = await client.GetImportedRowsAsync<ContactRow>(embedId, startRow: 0, count: 100);
    Console.WriteLine($"{page.Count} rows from {page.SheetMetadata.OriginalFileName}");

    // Or untyped, one dictionary per record keyed by template column key
    var untyped = await client.GetImportedRowsAsync(embedId);

    return page.Records;
});

For larger imports, the streaming variants page through all rows automatically:

await foreach (var contact in client.StreamImportedRowsAsync<ContactRow>(embedId, pageSize: 1000))
{
    // process each row
}

Non-success responses throw HttpRequestException with the StatusCode populated. AddOneSchemaApiClient returns an IHttpClientBuilder, so resilience or custom handlers can be chained, e.g. .AddStandardResilienceHandler().

Webhook Submission

The ExportWebhookRequest type is included for convenience for handling webhook exports, but no specific abstraction is provided for working with this type. To pull validated data via the API instead, use the OneSchema API Client.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  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. 
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.7.1 82 8/3/2026
0.7.0 82 8/3/2026
0.6.0 83 8/3/2026
0.5.0 90 7/31/2026
0.4.0 83 7/31/2026
0.3.0 121 4/30/2026
0.2.0 111 4/30/2026
0.1.0 126 3/30/2026