AlexaVoxCraft.InSkillPurchasing 7.0.2-beta.120

This is a prerelease version of AlexaVoxCraft.InSkillPurchasing.
dotnet add package AlexaVoxCraft.InSkillPurchasing --version 7.0.2-beta.120
                    
NuGet\Install-Package AlexaVoxCraft.InSkillPurchasing -Version 7.0.2-beta.120
                    
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="AlexaVoxCraft.InSkillPurchasing" Version="7.0.2-beta.120" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="AlexaVoxCraft.InSkillPurchasing" Version="7.0.2-beta.120" />
                    
Directory.Packages.props
<PackageReference Include="AlexaVoxCraft.InSkillPurchasing" />
                    
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 AlexaVoxCraft.InSkillPurchasing --version 7.0.2-beta.120
                    
#r "nuget: AlexaVoxCraft.InSkillPurchasing, 7.0.2-beta.120"
                    
#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 AlexaVoxCraft.InSkillPurchasing@7.0.2-beta.120
                    
#: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=AlexaVoxCraft.InSkillPurchasing&version=7.0.2-beta.120&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=AlexaVoxCraft.InSkillPurchasing&version=7.0.2-beta.120&prerelease
                    
Install as a Cake Tool

๐Ÿ”ฃ AlexaVoxCraft

AlexaVoxCraft is a modular C# .NET library for building Amazon Alexa skills using modern .NET practices. It provides comprehensive support for Alexa skill development with CQRS patterns, visual interfaces, and AWS Lambda hosting.

Key Features

  • ๐ŸŽฏ MediatR Integration: CQRS-style request handling with compile-time source-generated DI registration
  • ๐ŸŽจ APL Support: Complete Alexa Presentation Language implementation for rich visual interfaces
  • โšก Lambda Hosting: Optimized AWS Lambda runtime with custom serialization and ReadyToRun publishing
  • ๐Ÿ“Š Session Management: Robust session attribute handling with typed attribute serialization
  • ๐Ÿ”ง Pipeline Behaviors: Request/response interceptors for cross-cutting concerns like logging and validation
  • ๐Ÿ’ฐ In-Skill Purchasing: Full ISP support with buy/upsell/cancel directives and entitlement checks
  • ๐Ÿงช Testing Support: Comprehensive testing utilities with AutoFixture integration and property-based testing

๐Ÿ“ฆ Packages

Package NuGet Downloads
AlexaVoxCraft.Model NuGet Downloads
AlexaVoxCraft.Model.Apl NuGet Downloads
AlexaVoxCraft.MediatR NuGet Downloads
AlexaVoxCraft.Lambda NuGet Downloads
AlexaVoxCraft.MinimalLambda NuGet Downloads
AlexaVoxCraft.MediatR.Lambda NuGet Downloads
AlexaVoxCraft.Observability NuGet Downloads
AlexaVoxCraft.Smapi NuGet Downloads
AlexaVoxCraft.Model.InSkillPurchasing NuGet Downloads
AlexaVoxCraft.InSkillPurchasing NuGet Downloads

Build Status

๐Ÿš€ Quick Start

Install Core Packages

# Minimal Lambda hosting and MediatR integration
dotnet add package AlexaVoxCraft.MinimalLambda
dotnet add package AlexaVoxCraft.MediatR

# APL visual interface support (optional)
dotnet add package AlexaVoxCraft.Model.Apl

# OpenTelemetry observability (optional)
dotnet add package AlexaVoxCraft.Observability

# In-Skill Purchasing support (optional)
dotnet add package AlexaVoxCraft.InSkillPurchasing

# CloudWatch-compatible JSON logging (optional)
dotnet add package LayeredCraft.Logging.CompactJsonFormatter

Requirements

Supported target frameworks: .NET 8.0, .NET 9.0, .NET 10.0.

โš ๏ธ SDK Version Required: To use source-generated dependency injection with interceptors, you must use at least version 8.0.400 of the .NET SDK. This ships with Visual Studio 2022 version 17.11 or higher.

# Check your SDK version
dotnet --version
# Should show 8.0.400 or higher

Create a Basic Skill

// Program.cs (MinimalLambda host)
using AlexaVoxCraft.Lambda.Abstractions;
using AlexaVoxCraft.MediatR.DI;
using AlexaVoxCraft.MinimalLambda;
using AlexaVoxCraft.MinimalLambda.Extensions;
using AlexaVoxCraft.Model.Response;
using Amazon.Lambda.Core;
using MinimalLambda.Builder;

var builder = LambdaApplication.CreateBuilder();

// Handlers are automatically discovered and registered at compile time
builder.Services.AddSkillMediator(builder.Configuration);

// Register AlexaVoxCraft hosting services and handler
builder.Services.AddAlexaSkillHost<LambdaHandler, SkillRequest, SkillResponse>();

await using var app = builder.Build();
app.MapHandler(AlexaHandler.Invoke<SkillRequest, SkillResponse>);
return await app.RunAsync();

// Lambda handler bridges MediatR to the MinimalLambda host
public class LambdaHandler : ILambdaHandler<SkillRequest, SkillResponse>
{
    private readonly ISkillMediator _mediator;

    public LambdaHandler(ISkillMediator mediator) => _mediator = mediator;

    public Task<SkillResponse> HandleAsync(SkillRequest request, ILambdaContext context, CancellationToken cancellationToken) =>
        _mediator.Send(request, cancellationToken);
}

// Handler.cs
public class LaunchRequestHandler : IRequestHandler<LaunchRequest>
{
    public bool CanHandle(IHandlerInput handlerInput) =>
        handlerInput.RequestEnvelope.Request is LaunchRequest;

    public async Task<SkillResponse> Handle(IHandlerInput input, CancellationToken cancellationToken)
    {
        return await input.ResponseBuilder
            .Speak("Welcome to my skill!")
            .Reprompt("What would you like to do?")
            .GetResponse(cancellationToken);
    }
}

๐Ÿ“– Documentation

๐Ÿ“š Complete Documentation - Comprehensive guides and examples

Core Components

Examples

๐Ÿ“ Project Structure

AlexaVoxCraft/
โ”œโ”€โ”€ ๐Ÿ“‚ src/                                    # Core library packages
โ”‚   โ”œโ”€โ”€ ๐Ÿ“ฆ AlexaVoxCraft.Model/                # Base Alexa skill models & serialization
โ”‚   โ”œโ”€โ”€ ๐Ÿ“ฆ AlexaVoxCraft.Model.Apl/            # APL (Alexa Presentation Language) support
โ”‚   โ”œโ”€โ”€ ๐Ÿ“ฆ AlexaVoxCraft.MediatR/              # MediatR integration & request handling
โ”‚   โ”œโ”€โ”€ ๐Ÿ“ฆ AlexaVoxCraft.MediatR.Generators/   # Source generator for compile-time DI
โ”‚   โ”œโ”€โ”€ ๐Ÿ“ฆ AlexaVoxCraft.Lambda/               # Core Lambda abstractions & serialization
โ”‚   โ”œโ”€โ”€ ๐Ÿ“ฆ AlexaVoxCraft.MinimalLambda/        # MinimalLambda-based hosting for Alexa skills
โ”‚   โ”œโ”€โ”€ ๐Ÿ“ฆ AlexaVoxCraft.MediatR.Lambda/       # Legacy Lambda hosting (AlexaSkillFunction)
โ”‚   โ”œโ”€โ”€ ๐Ÿ“ฆ AlexaVoxCraft.Observability/        # OpenTelemetry instrumentation & telemetry
โ”‚   โ”œโ”€โ”€ ๐Ÿ“ฆ AlexaVoxCraft.Smapi/                # Skill Management API (SMAPI) client
โ”‚   โ”œโ”€โ”€ ๐Ÿ“ฆ AlexaVoxCraft.Model.InSkillPurchasing/ # ISP model objects: directives, response types, payment types
โ”‚   โ””โ”€โ”€ ๐Ÿ“ฆ AlexaVoxCraft.InSkillPurchasing/    # ISP runtime client (IInSkillPurchasingClient) for entitlement checks
โ”‚
โ”œโ”€โ”€ ๐Ÿ“‚ samples/                                # Working example projects
โ”‚   โ”œโ”€โ”€ ๐Ÿ“ฑ Sample.Skill.Function/              # Basic skill (legacy hosting)
โ”‚   โ”œโ”€โ”€ ๐Ÿ“ฑ Sample.Host.Function/               # Modern minimal API hosting
โ”‚   โ”œโ”€โ”€ ๐Ÿ“ฑ Sample.Generated.Function/          # Source-generated DI demonstration
โ”‚   โ”œโ”€โ”€ ๐Ÿ“ฑ Sample.Apl.Function/                # APL skill with visual interfaces
โ”‚   โ””โ”€โ”€ ๐Ÿ“ฑ Sample.Fact.InSkill.Purchases/      # Premium Fact skill demonstrating ISP buy/upsell/cancel flows
โ”‚
โ”œโ”€โ”€ ๐Ÿ“‚ test/                                   # Comprehensive test coverage
โ”‚   โ”œโ”€โ”€ ๐Ÿงช AlexaVoxCraft.Model.Tests/          # Core model & serialization tests
โ”‚   โ”œโ”€โ”€ ๐Ÿงช AlexaVoxCraft.Model.Apl.Tests/      # APL functionality tests
โ”‚   โ”œโ”€โ”€ ๐Ÿงช AlexaVoxCraft.MediatR.Tests/        # MediatR integration tests
โ”‚   โ”œโ”€โ”€ ๐Ÿงช AlexaVoxCraft.MediatR.Lambda.Tests/ # Lambda hosting tests
โ”‚   โ””โ”€โ”€ ๐Ÿงช AlexaVoxCraft.Smapi.Tests/          # SMAPI client tests
โ”‚
โ”œโ”€โ”€ ๐Ÿ“‚ AlexaVoxCraft.TestKit/                  # Testing utilities & AutoFixture support
โ””โ”€โ”€ ๐Ÿ“‚ docs/                                   # Documentation source

๐Ÿ›  Core Concepts

Request Handling Pattern

Skills use the MediatR pattern where:

  1. Requests implement IRequestHandler<T>
  2. Handlers optionally implement ICanHandle for routing logic
  3. Pipeline behaviors handle cross-cutting concerns (logging, exceptions)
  4. Lambda functions derive from AlexaSkillFunction<TRequest, TResponse>

Package Breakdown

Package Purpose Key Features
AlexaVoxCraft.Model Core Alexa models Request/response types, SSML, cards, directives, System.Text.Json serialization
AlexaVoxCraft.Model.Apl APL support 40+ components, commands, audio, vector graphics, extensions (DataStore, SmartMotion)
AlexaVoxCraft.MediatR Request handling Handler routing, pipeline behaviors, attributes management, DI integration
AlexaVoxCraft.MinimalLambda MinimalLambda hosting Minimal API-style hosting for Alexa skills, custom serialization, handler mapping
AlexaVoxCraft.MediatR.Lambda Legacy Lambda hosting AWS Lambda functions, context management, custom serialization, hosting extensions
AlexaVoxCraft.Observability OpenTelemetry integration Opt-in telemetry, metrics, spans, semantic attributes, ADOT/CloudWatch support
AlexaVoxCraft.Model.InSkillPurchasing ISP model objects BuyDirective, UpsellDirective, CancelDirective, ConnectionResponsePayload, PaymentType
AlexaVoxCraft.InSkillPurchasing ISP runtime client IInSkillPurchasingClient with GetProductsAsync/GetProductAsync, DI registration via AddInSkillPurchasing()

๐Ÿงช Testing

AlexaVoxCraft includes comprehensive testing support:

  • xUnit v3 with Microsoft.Testing.Platform
  • AutoFixture integration for property-based testing
  • AwesomeAssertions for fluent assertions
  • TestKit with specimen builders and test utilities

โš ๏ธ Error Handling

Implement the IExceptionHandler interface for centralized error handling:

public class GlobalExceptionHandler : IExceptionHandler
{
    public Task<bool> CanHandle(IHandlerInput handlerInput, Exception ex, CancellationToken cancellationToken)
    {
        return Task.FromResult(true); // Handle all exceptions
    }

    public Task<SkillResponse> Handle(IHandlerInput handlerInput, Exception ex, CancellationToken cancellationToken)
    {
        return handlerInput.ResponseBuilder
            .Speak("Sorry, something went wrong. Please try again.")
            .GetResponse(cancellationToken);
    }
}

๐Ÿ“‹ Version 4.0.0+ Breaking Changes

Cancellation Token Support

Version 4.0.0 introduces cancellation token support for Lambda functions. This is a breaking change that requires updates to your existing lambda handlers.

Required Changes

1. Update Lambda Handlers

Lambda handlers must now accept and pass the cancellation token:

// โŒ Before (v3.x)
public class LambdaHandler : ILambdaHandler<SkillRequest, SkillResponse>
{
    public async Task<SkillResponse> HandleAsync(SkillRequest input, ILambdaContext context)
    {
        return await _skillMediator.Send(input);
    }
}

// โœ… After (v4.0+)
public class LambdaHandler : ILambdaHandler<SkillRequest, SkillResponse>
{
    public async Task<SkillResponse> HandleAsync(SkillRequest input, ILambdaContext context, CancellationToken cancellationToken)
    {
        return await _skillMediator.Send(input, cancellationToken);
    }
}

2. Update AlexaSkillFunction Override

If you override FunctionHandlerAsync in your skill function class, you must update the signature:

// โŒ Before (v3.x)
public class MySkillFunction : AlexaSkillFunction<SkillRequest, SkillResponse>
{
    public override async Task<SkillResponse> FunctionHandlerAsync(SkillRequest input, ILambdaContext context)
    {
        // Custom logic here
        return await base.FunctionHandlerAsync(input, context);
    }
}

// โœ… After (v4.0+)
public class MySkillFunction : AlexaSkillFunction<SkillRequest, SkillResponse>
{
    public override async Task<SkillResponse> FunctionHandlerAsync(SkillRequest input, ILambdaContext context, CancellationToken cancellationToken)
    {
        // Custom logic here
        return await base.FunctionHandlerAsync(input, context, cancellationToken);
    }
}
Configuration Options

You can now configure the cancellation timeout buffer in your appsettings.json:

{
  "SkillConfiguration": {
    "SkillId": "amzn1.ask.skill.your-skill-id",
    "CancellationTimeoutBufferMilliseconds": 500
  }
}

The timeout buffer (default: 250ms) is subtracted from Lambda's remaining execution time to allow graceful shutdown and telemetry flushing.

๐Ÿ“‹ Version 5.1.0+ Changes

Overview

Version 5.1.0 (beta) introduces the AlexaVoxCraft.MinimalLambda package, replacing the previous AlexaVoxCraft.Lambda.Host host with a MinimalLambda-based experience. The legacy hosting approach remains fully supported for backward compatibility.

Package Restructuring

Version 5.1.0 refines the hosting lineup:

Package Purpose Status
AlexaVoxCraft.Lambda Core Lambda abstractions and serialization Active
AlexaVoxCraft.MinimalLambda MinimalLambda-based hosting New (recommended for new projects)
AlexaVoxCraft.MediatR.Lambda Legacy Lambda hosting with AlexaSkillFunction Existing (still fully supported)

Namespace Changes

Core Lambda abstractions have been moved to the new AlexaVoxCraft.Lambda package:

Classes Moved:

  • ILambdaHandler<TRequest, TResponse>
  • HandlerDelegate<TRequest, TResponse>
  • AlexaLambdaSerializer
  • SystemTextDestructuringPolicy

Removed Obsolete Classes

The following obsolete class has been removed:

PollyVoices - This class was marked as obsolete in previous versions and has been removed. Use the AlexaSupportedVoices class instead for Amazon Polly voice name constants.

Before (v4.x):

using AlexaVoxCraft.MediatR.Lambda.Abstractions;
using AlexaVoxCraft.MediatR.Lambda.Serialization;

public class LambdaHandler : ILambdaHandler<SkillRequest, SkillResponse>
{
    // Implementation
}

After (v5.0+):

using AlexaVoxCraft.Lambda.Abstractions;
using AlexaVoxCraft.Lambda.Serialization;

public class LambdaHandler : ILambdaHandler<SkillRequest, SkillResponse>
{
    // Implementation
}

Two Hosting Approaches

Version 5.1.0 provides two ways to host Alexa skills in AWS Lambda:

Use AlexaVoxCraft.MinimalLambda for a familiar minimal API-style hosting experience:

dotnet add package AlexaVoxCraft.MinimalLambda
// Program.cs
using AlexaVoxCraft.Lambda.Abstractions;
using AlexaVoxCraft.MediatR.DI;
using AlexaVoxCraft.MinimalLambda;
using AlexaVoxCraft.MinimalLambda.Extensions;
using AlexaVoxCraft.Model.Response;
using MinimalLambda.Builder;

var builder = LambdaApplication.CreateBuilder();

builder.Services.AddSkillMediator(builder.Configuration);
builder.Services.AddAlexaSkillHost<LambdaHandler, SkillRequest, SkillResponse>();

await using var app = builder.Build();
app.MapHandler(AlexaHandler.Invoke<SkillRequest, SkillResponse>);
await app.RunAsync();

Benefits:

  • Familiar minimal API-style builder pattern (similar to ASP.NET Core)
  • Lean MinimalLambda runtime keeps cold starts small
  • More flexible service configuration
  • Better separation of concerns
Legacy Approach (Existing Projects)

Continue using AlexaVoxCraft.MediatR.Lambda with the AlexaSkillFunction pattern:

dotnet add package AlexaVoxCraft.MediatR.Lambda
// Program.cs
using AlexaVoxCraft.MediatR.Lambda;

return await LambdaHostExtensions.RunAlexaSkill<MySkillFunction, SkillRequest, SkillResponse>();

// Function.cs
public class MySkillFunction : AlexaSkillFunction<SkillRequest, SkillResponse>
{
    protected override void Init(IHostBuilder builder)
    {
        builder
            .UseHandler<LambdaHandler, SkillRequest, SkillResponse>()
            .ConfigureServices((context, services) =>
            {
                services.AddSkillMediator(context.Configuration);
            });
    }
}

This approach remains fully supported and requires no migration for existing projects.

Migration Impact

Who is affected:

  • Projects directly referencing AlexaVoxCraft.MediatR.Lambda.Abstractions namespace
  • Projects directly referencing AlexaVoxCraft.MediatR.Lambda.Serialization namespace
  • Projects wanting to adopt the modern hosting approach

Who is NOT affected:

  • Projects using AlexaSkillFunction<TRequest, TResponse> without directly referencing moved classes
  • Projects that don't import the moved namespaces

Required Actions for Migration

1. Update Namespace Imports

If you directly reference the moved classes, update your using statements:

// Change this:
using AlexaVoxCraft.MediatR.Lambda.Abstractions;
using AlexaVoxCraft.MediatR.Lambda.Serialization;

// To this:
using AlexaVoxCraft.Lambda.Abstractions;
using AlexaVoxCraft.Lambda.Serialization;
2. (Optional) Migrate to Modern Hosting

To adopt the new minimal API-style hosting:

  1. Replace package reference:

    dotnet remove package AlexaVoxCraft.MediatR.Lambda
    dotnet add package AlexaVoxCraft.MinimalLambda
    dotnet add package AlexaVoxCraft.MediatR
    
  2. Remove AwsLambda.Host interceptor namespaces (not needed with MinimalLambda) and keep your generator interceptors.

  3. Refactor Program.cs to use builder pattern (see example above)

  4. Remove Function class inheriting from AlexaSkillFunction

For detailed migration guidance, see the Lambda Hosting documentation.

๐Ÿ“‹ Version 6.0.0+ Breaking Changes

APL Collection Type System Overhaul

Version 6.0.0 introduces a major redesign of the APL collection type system with breaking changes that affect how you work with APL component arrays and collections.

What Changed

1. Migration from APLValue<IList<T>> to APLValueCollection<T>

All properties that previously used APLValue<IList<T>> or APLValue<List<T>> have been migrated to APLValueCollection<T>:

// โŒ Before (v5.x)
public APLValue<IList<APLComponent>>? Items { get; set; }
public APLValue<List<int>>? Padding { get; set; }

// โœ… After (v6.0.0+)
public APLValueCollection<APLComponent>? Items { get; set; }
public APLValueCollection<int>? Padding { get; set; }

2. APLValue<T> No Longer Supports Collection Types

APLValue<T> now throws InvalidOperationException in its static constructor if T is any collection type (IEnumerable, ICollection, IList, List, etc.):

// โŒ This will throw InvalidOperationException at runtime
var value = new APLValue<List<string>>();

// โœ… Use APLValueCollection<T> instead
var collection = new APLValueCollection<string>();

3. APLValueCollection<T> Implements IList<T>

APLValueCollection<T> now implements IList<T> and IReadOnlyList<T> directly, providing natural collection ergonomics:

// โŒ Before (v5.x) - awkward .Items property access
collection.Items!.Add(new Text());
var count = collection.Items?.Count ?? 0;
var first = collection.Items![0];

// โœ… After (v6.0.0+) - direct collection operations
collection.Add(new Text());
var count = collection.Count;
var first = collection[0];

4. Items Property Changed to Read-Only

The Items property is now IReadOnlyList<T> (read-only) instead of IList<T>? (settable):

// โŒ Before (v5.x) - Items was settable
collection.Items = new List<APLComponent> { new Text() };

// โœ… After (v6.0.0+) - Items is read-only, use constructor or Add()
var collection = new APLValueCollection<APLComponent>([new Text()]);
// OR
collection.Add(new Text());

5. Materialize Pattern for Expression Handling

Mutations automatically clear the Expression property when you modify the collection:

// Expression-backed collection (data binding)
APLValueCollection<APLComponent> collection = "${data.items}";
collection.Expression; // "${data.items}"

// Mutation materializes the collection
collection.Add(new Text());
collection.Expression; // null - expression cleared

// Read operations preserve the expression
var count = collection.Count;  // Does NOT clear Expression
var contains = collection.Contains(item);  // Does NOT clear Expression
Migration Guide

Step 1: Update Property Types

If you have custom APL components or are working with the model directly:

// Change this:
public APLValue<IList<APLComponent>>? Children { get; set; }
public APLValue<List<int>>? Padding { get; set; }

// To this:
public APLValueCollection<APLComponent>? Children { get; set; }
public APLValueCollection<int>? Padding { get; set; }

Step 2: Remove .Items Property Access for Mutations

// โŒ Before (v5.x)
container.Items!.Add(new Text { Content = "Hello" });
container.Items!.Clear();
var component = container.Items![0];

// โœ… After (v6.0.0+)
container.Add(new Text { Content = "Hello" });
container.Clear();
var component = container[0];

Step 3: Update Collection Initialization

Collections can be initialized using collection expressions (C# 12), constructors, or implicit conversions:

// Collection expression (C# 12+)
APLValueCollection<APLComponent> items = [
    new Text { Content = "Item 1" },
    new Text { Content = "Item 2" }
];

// Constructor with IEnumerable
var list = new List<APLComponent> { new Text() };
var collection = new APLValueCollection<APLComponent>(list);

// Implicit conversion
APLValueCollection<APLComponent> fromList = new List<APLComponent> { new Text() };
APLValueCollection<APLComponent> fromArray = new[] { new Text() };
APLValueCollection<APLComponent> fromExpression = "${data.items}";

Step 4: Be Aware of Materialize Behavior

If you're using expression-based binding and then mutating the collection:

// This will clear the expression
var collection = new APLValueCollection<APLComponent> { Expression = "${data.items}" };
collection.Add(new Text());  // Expression is now null

// If you need to preserve expression mode, don't mutate the collection
// Use expression-only or items-only, not both
What Still Works
  • โœ… Collection expressions: [item1, item2, item3]
  • โœ… LINQ operations: collection.OfType<Text>().ToList()
  • โœ… Foreach enumeration: foreach (var item in collection)
  • โœ… Implicit conversions from List<T>, T[], and string
  • โœ… Expression-based data binding: "${data.items}"
  • โœ… JSON serialization (unchanged behavior)
  • โœ… The Items property for read-only access and inspection
Benefits of This Change
  1. Natural API: Direct collection operations without .Items indirection
  2. Type Safety: Cannot accidentally use APLValue<List<T>> anymore
  3. Better IntelliSense: Collection methods appear directly on the type
  4. Clearer Intent: Materialize pattern makes expression/items relationship explicit
  5. Standard .NET Patterns: Implements IList<T> like other collection types
Impact Summary

High Impact:

  • Custom APL component implementations
  • Code directly manipulating APL collections
  • Any code using APLValue<IList<T>> or APLValue<List<T>>

Low Impact:

  • Most skill code using the document builder API
  • Code that only reads from collections
  • Expression-based data binding (still works)

๐Ÿ“‹ Version 7.0.0+ Breaking Changes

Attribute System Overhaul: JsonElement-Based Storage

Version 7.0.0 replaces the untyped Dictionary<string, object> attribute system with a System.Text.Json-native Dictionary<string, JsonElement> model throughout. This is a breaking change affecting model types, IAttributesManager, and IPersistenceAdapter.

What Changed

1. Session.Attributes and SkillResponse.SessionAttributes โ€” AlexaVoxCraft.Model

Both properties changed from Dictionary<string, object> to Dictionary<string, JsonElement>:

// โŒ Before (v6.x)
Dictionary<string, object> attributes = request.Session.Attributes;
Dictionary<string, object>? sessionAttributes = response.SessionAttributes;

// โœ… After (v7.0+)
Dictionary<string, JsonElement> attributes = request.Session.Attributes;
Dictionary<string, JsonElement>? sessionAttributes = response.SessionAttributes;

Access values using the JsonElement API:

// โŒ Before (v6.x)
var score = (int)sessionAttributes["currentScore"];

// โœ… After (v7.0+)
var score = sessionAttributes["currentScore"].GetInt32();

2. IAttributesManager โ€” Complete Redesign โ€” AlexaVoxCraft.MediatR

The old async get/set methods are replaced by synchronous JsonAttributeBag properties and explicit typed helpers:

// โŒ Before (v6.x)
var session = await input.AttributesManager.GetSessionAttributes(cancellationToken);
session["currentScore"] = 42;
await input.AttributesManager.SetSessionAttributes(session, cancellationToken);

var request = await input.AttributesManager.GetRequestAttributes(cancellationToken);
request["tempKey"] = "value";

var persistent = await input.AttributesManager.GetPersistentAttributes(cancellationToken);
persistent["totalGames"] = 10;
await input.AttributesManager.SetPersistentAttributes(persistent, cancellationToken);

// โœ… After (v7.0+)
// Session โ€” synchronous, typed
input.AttributesManager.Session.Set("currentScore", 42);
var score = input.AttributesManager.Session.Get<int>("currentScore");

// Request โ€” synchronous, typed
input.AttributesManager.Request.Set("tempKey", "value");

// Persistent โ€” still async, returns JsonAttributeBag
var persistent = await input.AttributesManager.GetPersistentAsync(cancellationToken);
persistent.Set("totalGames", 10);
await input.AttributesManager.SavePersistentAttributes(cancellationToken);

Shorthand typed session state methods are also available directly on IAttributesManager:

input.AttributesManager.SetSessionState("gameStarted", true);
var started = input.AttributesManager.GetSessionState<bool>("gameStarted");

if (input.AttributesManager.TryGetSessionState<GameState>("state", out var state))
{
    // use state
}

input.AttributesManager.ClearSessionState("gameStarted");

3. IPersistenceAdapter โ€” AlexaVoxCraft.MediatR

Method signatures changed from IDictionary<string, object> to IDictionary<string, JsonElement>:

// โŒ Before (v6.x)
public interface IPersistenceAdapter
{
    Task<IDictionary<string, object>> GetAttributes(SkillRequest requestEnvelope, CancellationToken cancellationToken = default);
    Task SaveAttribute(SkillRequest requestEnvelope, IDictionary<string, object> attributes, CancellationToken cancellationToken = default);
}

// โœ… After (v7.0+)
public interface IPersistenceAdapter
{
    Task<IDictionary<string, JsonElement>> GetAttributes(SkillRequest requestEnvelope, CancellationToken cancellationToken = default);
    Task SaveAttribute(SkillRequest requestEnvelope, IDictionary<string, JsonElement> attributes, CancellationToken cancellationToken = default);
}

Any existing IPersistenceAdapter implementations must update their return type and parameter type, and serialize/deserialize values as JsonElement.

4. Removed Types

The following type has been removed with no replacement:

Removed Type Package Notes
DictionaryExtensions AlexaVoxCraft.Model.Apl Internal APL helper; no public replacement needed.
Migration Guide

Step 1: Update IPersistenceAdapter implementations

// Change return and parameter types
public async Task<IDictionary<string, JsonElement>> GetAttributes(
    SkillRequest requestEnvelope, CancellationToken cancellationToken = default)
{
    // Deserialize stored data to Dictionary<string, JsonElement>
    var json = await _store.GetAsync(requestEnvelope.Session.User.UserId, cancellationToken);
    return string.IsNullOrEmpty(json)
        ? new Dictionary<string, JsonElement>()
        : JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(json)!;
}

public async Task SaveAttribute(SkillRequest requestEnvelope,
    IDictionary<string, JsonElement> attributes, CancellationToken cancellationToken = default)
{
    var json = JsonSerializer.Serialize(attributes);
    await _store.SaveAsync(requestEnvelope.Session.User.UserId, json, cancellationToken);
}

Step 2: Replace GetSessionAttributes / SetSessionAttributes calls

// โŒ Before
var attrs = await input.AttributesManager.GetSessionAttributes(cancellationToken);
attrs.TryGetAttribute<int>("score", out var score);
attrs.SetAttribute("score", score + 1);
await input.AttributesManager.SetSessionAttributes(attrs, cancellationToken);

// โœ… After โ€” session is auto-saved by DefaultResponseBuilder
input.AttributesManager.TryGetSessionState<int>("score", out var score);
input.AttributesManager.SetSessionState("score", score + 1);

Step 3: Replace raw Session.Attributes access

// โŒ Before
var value = (int)request.Session.Attributes["score"];
var text = request.Session.Attributes["name"] as string;

// โœ… After
var value = request.Session.Attributes["score"].GetInt32();
var text = request.Session.Attributes["name"].GetString();
Impact Summary

High Impact:

  • Custom IPersistenceAdapter implementations
  • Code reading from or writing to Session.Attributes or SkillResponse.SessionAttributes directly
  • Code calling the old IAttributesManager get/set methods

Low Impact:

  • Skills that only use input.ResponseBuilder and don't directly inspect session attributes
  • Code using input.AttributesManager only for persistent attributes (API shape is preserved, types updated)

๐Ÿค Contributing

PRs are welcome! Please submit issues and ideas to help make this toolkit even better.

๐Ÿ“œ Credits & Attribution

๐Ÿ“ฆ Credits:

๐Ÿ“œ License

This project is licensed under the MIT License.

Stargazers over time

Stargazers over time

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  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
7.0.2-beta.120 0 3/4/2026
7.0.1-beta.119 34 3/3/2026
7.0.0-beta.118 36 3/3/2026