AlexaVoxCraft.InSkillPurchasing
7.0.2-beta.120
dotnet add package AlexaVoxCraft.InSkillPurchasing --version 7.0.2-beta.120
NuGet\Install-Package AlexaVoxCraft.InSkillPurchasing -Version 7.0.2-beta.120
<PackageReference Include="AlexaVoxCraft.InSkillPurchasing" Version="7.0.2-beta.120" />
<PackageVersion Include="AlexaVoxCraft.InSkillPurchasing" Version="7.0.2-beta.120" />
<PackageReference Include="AlexaVoxCraft.InSkillPurchasing" />
paket add AlexaVoxCraft.InSkillPurchasing --version 7.0.2-beta.120
#r "nuget: AlexaVoxCraft.InSkillPurchasing, 7.0.2-beta.120"
#:package AlexaVoxCraft.InSkillPurchasing@7.0.2-beta.120
#addin nuget:?package=AlexaVoxCraft.InSkillPurchasing&version=7.0.2-beta.120&prerelease
#tool nuget:?package=AlexaVoxCraft.InSkillPurchasing&version=7.0.2-beta.120&prerelease
๐ฃ 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
๐ 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
- Request Handling - MediatR integration and handler patterns
- Source Generation - Compile-time DI registration with C# interceptors
- APL Integration - Rich visual interface development
- Lambda Hosting - AWS Lambda deployment and optimization
- Session Management - State persistence and user data
- Pipeline Behaviors - Cross-cutting concerns and interceptors
Examples
- Complete Examples - Production-ready trivia skill implementation
๐ 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:
- Requests implement
IRequestHandler<T> - Handlers optionally implement
ICanHandlefor routing logic - Pipeline behaviors handle cross-cutting concerns (logging, exceptions)
- 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>AlexaLambdaSerializerSystemTextDestructuringPolicy
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:
๐ Modern Approach (Recommended for New Projects)
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.Abstractionsnamespace - Projects directly referencing
AlexaVoxCraft.MediatR.Lambda.Serializationnamespace - 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:
Replace package reference:
dotnet remove package AlexaVoxCraft.MediatR.Lambda dotnet add package AlexaVoxCraft.MinimalLambda dotnet add package AlexaVoxCraft.MediatRRemove
AwsLambda.Hostinterceptor namespaces (not needed with MinimalLambda) and keep your generator interceptors.Refactor Program.cs to use builder pattern (see example above)
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[], andstring - โ
Expression-based data binding:
"${data.items}" - โ JSON serialization (unchanged behavior)
- โ
The
Itemsproperty for read-only access and inspection
Benefits of This Change
- Natural API: Direct collection operations without
.Itemsindirection - Type Safety: Cannot accidentally use
APLValue<List<T>>anymore - Better IntelliSense: Collection methods appear directly on the type
- Clearer Intent: Materialize pattern makes expression/items relationship explicit
- 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>>orAPLValue<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
IPersistenceAdapterimplementations - Code reading from or writing to
Session.AttributesorSkillResponse.SessionAttributesdirectly - Code calling the old
IAttributesManagerget/set methods
Low Impact:
- Skills that only use
input.ResponseBuilderand don't directly inspect session attributes - Code using
input.AttributesManageronly 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:
- Core Alexa skill models (
AlexaVoxCraft.Model) based on timheuer/alexa-skills-dotnet- APL support (
AlexaVoxCraft.Model.Apl) based on stoiveyp/Alexa.NET.APL
๐ License
This project is licensed under the MIT License.
Stargazers over time
| Product | Versions 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. |
-
net10.0
- AlexaVoxCraft.Http (>= 7.0.2-beta.120)
- AlexaVoxCraft.MediatR (>= 7.0.2-beta.120)
-
net8.0
- AlexaVoxCraft.Http (>= 7.0.2-beta.120)
- AlexaVoxCraft.MediatR (>= 7.0.2-beta.120)
-
net9.0
- AlexaVoxCraft.Http (>= 7.0.2-beta.120)
- AlexaVoxCraft.MediatR (>= 7.0.2-beta.120)
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 |