RshipSdk 1.0.19

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

RshipSdk for .NET

RshipSdk is a comprehensive, high-level SDK for building real-time applications with the Rship platform. It provides a powerful abstraction layer for instance management, bidirectional action handling, real-time data emission, and seamless integration with the Rship ecosystem.

🚀 Features

  • 🏗️ Instance Management: Create and manage distributed application instances with automatic registration
  • ⚡ Action Handling: Define type-safe, async action handlers with automatic JSON schema generation
  • 📡 Real-time Emitters: Send structured data through typed emitters with WebSocket communication
  • 🎯 Target Management: Organize functionality into logical, hierarchical targets
  • 🔒 Type Safety: Full C# type safety with automatic JSON schema validation
  • 🔄 Auto-reconnection: Resilient WebSocket connections with automatic state restoration
  • 📝 Comprehensive Logging: Built-in logging support via Microsoft.Extensions.Logging
  • 🔧 Async/Await: Modern async patterns throughout the entire API
  • 🎨 Flexible Architecture: Modular design supporting complex, distributed applications

📦 Installation

Package Manager

dotnet add package RshipSdk

PackageReference

<PackageReference Include="RshipSdk" Version="1.0.1" />

.NET CLI

Install-Package RshipSdk

🏃‍♂️ Quick Start

Here's a complete example showing the core concepts:

using Microsoft.Extensions.Logging;
using RshipSdk;

// 1. Define your data models
public class PlayVideoData
{
    public string VideoPath { get; set; } = string.Empty;
    public bool AutoPlay { get; set; } = true;
    public double Volume { get; set; } = 1.0;
}

public class VideoStatusData  
{
    public string Status { get; set; } = string.Empty;
    public double Progress { get; set; }
    public TimeSpan Duration { get; set; }
}

// 2. Setup logging (recommended)
using var loggerFactory = LoggerFactory.Create(builder =>
    builder.AddConsole().SetMinimumLevel(LogLevel.Information));
var logger = loggerFactory.CreateLogger<SdkClient>();

// Create and configure the SDK client
var sdk = SdkClient.Init(logger);
await sdk.SetAddressAsync("ws://localhost:8080/myko");
await sdk.AwaitConnectionAsync();

// Create an instance
var instance = await sdk.AddInstanceAsync(new InstanceArgs
{
    Name = "My Application",
    ShortId = "my-app",
    Code = "my-app-code",
    ServiceId = "my-service",
    Color = "#FF0000",
    MachineId = "my-machine",
    Status = InstanceStatus.Available
});

// Create a target
var target = await instance.AddTargetAsync(new TargetArgs
{
    Name = "My Target",
    ShortId = "my-target", 
    Category = "automation"
});

// Add an action handler
await target.AddActionAsync(
    ActionArgs<MyActionData>.New("Process Data", "process-data"),
    async (action, data) =>
    {
        Console.WriteLine($"Processing: {data.Data}");
        // Your action logic here
    }
);

// Add an emitter
var emitter = await target.AddEmitterAsync(
    EmitterArgs<MyEmitterType>.New("Status Updates", "status")
);

// Send data via emitter
await emitter.PulseAsync(new MyEmitterType 
{ 
    Data = "Hello from C#!" 
});

📚 API Reference

SdkClient

The main entry point for the SDK. Manages WebSocket connections and provides methods to create instances.

Methods
static SdkClient Init(ILogger<SdkClient>? logger = null)

Creates a new SDK client instance.

// Without logging
var sdk = SdkClient.Init();

// With logging
using var loggerFactory = LoggerFactory.Create(builder =>
    builder.AddConsole().SetMinimumLevel(LogLevel.Information));
var logger = loggerFactory.CreateLogger<SdkClient>();
var sdk = SdkClient.Init(logger);
async Task SetAddressAsync(string url)

Connects to the Rship server at the specified WebSocket URL.

await sdk.SetAddressAsync("ws://localhost:8080/myko");
async Task AwaitConnectionAsync()

Waits until the WebSocket connection is successfully established.

await sdk.AwaitConnectionAsync();
async Task<InstanceProxy> AddInstanceAsync(InstanceArgs args)

Creates and registers a new service instance.

var instance = await sdk.AddInstanceAsync(new InstanceArgs
{
    Name = "My Service",
    ShortId = "my-service-instance", 
    Code = "my-service-code",
    ServiceId = "my-service",
    Color = "#FF5722",
    MachineId = "my-machine",
    Status = InstanceStatus.Available
});

InstanceArgs

Configuration for creating a service instance.

Properties
Property Type Description
Name string Human-readable name for the instance
ShortId string Unique short identifier for the instance
Code string Service type code
ServiceId string Service identifier
ClusterId string? Optional cluster identifier
Color string Hex color code for UI representation
MachineId string Machine identifier where instance runs
Message string? Optional status message
Status InstanceStatus Current instance status
InstanceStatus Enum
public enum InstanceStatus
{
    Available,  // Instance is ready to process requests
    Busy,      // Instance is currently processing
    Offline,   // Instance is not available
    Error      // Instance encountered an error
}

InstanceProxy

Represents a registered service instance and provides methods to manage targets.

Methods
async Task<TargetProxy> AddTargetAsync(TargetArgs args)

Creates and registers a new target within this instance.

var target = await instance.AddTargetAsync(new TargetArgs
{
    Name = "Video Controller",
    ShortId = "video-ctrl",
    Category = "media"
});
Properties
Property Type Description
ShortId string The instance's short identifier
ServiceId string The service identifier

TargetArgs

Configuration for creating a target.

Properties
Property Type Description
Name string Human-readable name for the target
ShortId string Unique short identifier for the target
Category string Category for organizing targets

TargetProxy

Represents a registered target and provides methods to manage actions and emitters.

Methods
async Task<ActionProxy<T>> AddActionAsync<T>(ActionArgs<T> args, Func<Action, T, Task> handler)

Adds a typed action handler to the target.

await target.AddActionAsync(
    ActionArgs<PlayVideoData>.New("Play Video", "play-video"),
    async (action, data) =>
    {
        Console.WriteLine($"Playing video: {data.VideoPath}");
        // Your video playback logic here
        await PlayVideoAsync(data.VideoPath, data.Volume);
    }
);
async Task<EmitterProxy<T>> AddEmitterAsync<T>(EmitterArgs<T> args)

Adds a typed emitter to the target for sending data.

var statusEmitter = await target.AddEmitterAsync(
    EmitterArgs<VideoStatusData>.New("Video Status", "video-status")
);
Properties
Property Type Description
ShortId string The target's short identifier
Instance InstanceProxy Reference to the parent instance

ActionArgs<T>

Configuration for creating a typed action.

Methods
static ActionArgs<T> New(string name, string shortId)

Creates a new action configuration.

var actionArgs = ActionArgs<MyDataType>.New("Process Data", "process-data");
Properties
Property Type Description
Name string Human-readable name for the action
ShortId string Unique short identifier for the action

ActionProxy<T>

Represents a registered action handler.

Properties
Property Type Description
ShortId string The action's short identifier

EmitterArgs<T>

Configuration for creating a typed emitter.

Methods
static EmitterArgs<T> New(string name, string shortId)

Creates a new emitter configuration.

var emitterArgs = EmitterArgs<StatusData>.New("Status Updates", "status");
Properties
Property Type Description
Name string Human-readable name for the emitter
ShortId string Unique short identifier for the emitter

EmitterProxy<T>

Represents a registered emitter for sending typed data.

Methods
async Task PulseAsync(T data)

Sends data through the emitter to connected clients.

await emitter.PulseAsync(new VideoStatusData
{
    Status = "playing",
    Progress = 0.5,
    Duration = TimeSpan.FromMinutes(10)
});
Properties
Property Type Description
ShortId string The emitter's short identifier

💡 Complete Examples

Video Player Service

using Microsoft.Extensions.Logging;
using RshipSdk;
using RshipSdk.Entities;
using System.Text.Json.Serialization;

// Define data models
public class PlayVideoRequest
{
    [JsonPropertyName("videoPath")]
    public string VideoPath { get; set; } = "";
    
    [JsonPropertyName("volume")]
    public double Volume { get; set; } = 1.0;
    
    [JsonPropertyName("autoPlay")]
    public bool AutoPlay { get; set; } = true;
}

public class VideoStatus
{
    [JsonPropertyName("status")]
    public string Status { get; set; } = "";
    
    [JsonPropertyName("progress")]
    public double Progress { get; set; }
    
    [JsonPropertyName("duration")]
    public double Duration { get; set; }
}

public class VideoPlayerService
{
    private EmitterProxy<VideoStatus>? _statusEmitter;
    
    public async Task StartAsync()
    {
        // Setup logging
        using var loggerFactory = LoggerFactory.Create(builder =>
            builder.AddConsole().SetMinimumLevel(LogLevel.Information));
        var logger = loggerFactory.CreateLogger<SdkClient>();

        // Initialize SDK
        var sdk = SdkClient.Init(logger);
        await sdk.SetAddressAsync("ws://localhost:8080/myko");
        await sdk.AwaitConnectionAsync();

        // Create instance
        var instance = await sdk.AddInstanceAsync(new InstanceArgs
        {
            Name = "Video Player Service",
            ShortId = "video-player-001",
            Code = "video-player",
            ServiceId = "video-service",
            Color = "#9C27B0",
            MachineId = Environment.MachineName,
            Status = InstanceStatus.Available
        });

        // Create target
        var videoTarget = await instance.AddTargetAsync(new TargetArgs
        {
            Name = "Video Controller",
            ShortId = "video-ctrl",
            Category = "media"
        });

        // Add play action
        await videoTarget.AddActionAsync(
            ActionArgs<PlayVideoRequest>.New("Play Video", "play"),
            HandlePlayVideo
        );

        // Add pause action  
        await videoTarget.AddActionAsync(
            ActionArgs<object>.New("Pause Video", "pause"),
            HandlePauseVideo
        );

        // Add status emitter
        _statusEmitter = await videoTarget.AddEmitterAsync(
            EmitterArgs<VideoStatus>.New("Video Status", "status")
        );

        Console.WriteLine("Video Player Service is ready!");
        
        // Keep running
        await Task.Delay(Timeout.Infinite);
    }

    private async Task HandlePlayVideo(Entities.Action action, PlayVideoRequest request)
    {
        Console.WriteLine($"Playing video: {request.VideoPath} at volume {request.Volume}");
        
        // Simulate video playback
        await EmitStatus("playing", 0.0, 300.0);
        
        // Your actual video playback logic would go here
        await SimulateVideoPlayback();
    }

    private async Task HandlePauseVideo(Entities.Action action, object data)
    {
        Console.WriteLine("Pausing video");
        await EmitStatus("paused", 0.25, 300.0);
    }

    private async Task EmitStatus(string status, double progress, double duration)
    {
        if (_statusEmitter != null)
        {
            await _statusEmitter.PulseAsync(new VideoStatus
            {
                Status = status,
                Progress = progress,
                Duration = duration
            });
        }
    }

    private async Task SimulateVideoPlayback()
    {
        // Simulate progress updates
        for (double progress = 0; progress <= 1.0; progress += 0.1)
        {
            await EmitStatus("playing", progress, 300.0);
            await Task.Delay(1000);
        }
        
        await EmitStatus("finished", 1.0, 300.0);
    }
}

IoT Sensor Monitor

public class SensorData
{
    [JsonPropertyName("temperature")]
    public double Temperature { get; set; }
    
    [JsonPropertyName("humidity")]
    public double Humidity { get; set; }
    
    [JsonPropertyName("timestamp")]
    public DateTime Timestamp { get; set; }
}

public class CalibrationRequest
{
    [JsonPropertyName("temperatureOffset")]
    public double TemperatureOffset { get; set; }
    
    [JsonPropertyName("humidityOffset")]
    public double HumidityOffset { get; set; }
}

public class IoTSensorService
{
    private EmitterProxy<SensorData>? _sensorEmitter;
    private readonly Random _random = new();
    private double _tempOffset = 0;
    private double _humidityOffset = 0;

    public async Task StartAsync()
    {
        var sdk = SdkClient.Init();
        await sdk.SetAddressAsync("ws://localhost:8080/myko");
        await sdk.AwaitConnectionAsync();

        var instance = await sdk.AddInstanceAsync(new InstanceArgs
        {
            Name = "IoT Sensor Monitor",
            ShortId = "iot-sensor-001",
            Code = "iot-sensor",
            ServiceId = "iot-service", 
            Color = "#4CAF50",
            MachineId = Environment.MachineName,
            Status = InstanceStatus.Available
        });

        var sensorTarget = await instance.AddTargetAsync(new TargetArgs
        {
            Name = "Environmental Sensor",
            ShortId = "env-sensor",
            Category = "iot"
        });

        // Add calibration action
        await sensorTarget.AddActionAsync(
            ActionArgs<CalibrationRequest>.New("Calibrate Sensor", "calibrate"),
            async (action, request) =>
            {
                _tempOffset = request.TemperatureOffset;
                _humidityOffset = request.HumidityOffset;
                Console.WriteLine($"Sensor calibrated: Temp offset {_tempOffset}°C, Humidity offset {_humidityOffset}%");
            }
        );

        // Add sensor data emitter
        _sensorEmitter = await sensorTarget.AddEmitterAsync(
            EmitterArgs<SensorData>.New("Sensor Readings", "readings")
        );

        // Start sensor reading loop
        _ = Task.Run(ReadSensorDataLoop);

        Console.WriteLine("IoT Sensor Service is running!");
        await Task.Delay(Timeout.Infinite);
    }

    private async Task ReadSensorDataLoop()
    {
        while (true)
        {
            var sensorData = new SensorData
            {
                Temperature = 20 + _random.NextDouble() * 10 + _tempOffset, // 20-30°C
                Humidity = 40 + _random.NextDouble() * 20 + _humidityOffset, // 40-60%
                Timestamp = DateTime.UtcNow
            };

            if (_sensorEmitter != null)
            {
                await _sensorEmitter.PulseAsync(sensorData);
            }

            await Task.Delay(5000); // Read every 5 seconds
        }
    }
}

Core Concepts

SdkClient

The main entry point for the SDK. Manages WebSocket connections to the Rship server and handles automatic reconnection. All instances are created through the SDK client.

Instance

Represents a service instance in the Rship ecosystem. Each instance has a unique identifier and can contain multiple targets. Instances automatically register with the server and maintain their status.

Target

A logical grouping of actions and emitters within an instance. Targets represent controllable entities in your application and organize functionality by category (e.g., "media", "automation", "iot").

Actions

Incoming message handlers that define what your application can do when triggered by the Rship platform. Actions are strongly typed and include automatic JSON schema generation for validation.

Emitters

Outgoing message producers that send structured data from your application to the Rship platform. Emitters support real-time data streaming to connected clients.

🔧 Advanced Configuration

Environment Variables

The SDK supports the following environment variables:

Variable Default Description
RSHIP_ADDRESS localhost Rship server hostname
RSHIP_PORT 8080 Rship server port

Custom Logging Configuration

using Microsoft.Extensions.Logging;

// Console logging with custom formatting
var loggerFactory = LoggerFactory.Create(builder =>
{
    builder
        .AddConsole(options =>
        {
            options.IncludeScopes = true;
            options.TimestampFormat = "[yyyy-MM-dd HH:mm:ss] ";
        })
        .AddDebug()
        .SetMinimumLevel(LogLevel.Debug)
        .AddFilter("RshipSdk", LogLevel.Information)
        .AddFilter("MykoSdk", LogLevel.Warning);
});

var logger = loggerFactory.CreateLogger<SdkClient>();
var sdk = SdkClient.Init(logger);

Connection Management

// Wait for connection with timeout
var connectionTask = sdk.AwaitConnectionAsync();
var timeoutTask = Task.Delay(TimeSpan.FromSeconds(30));

if (await Task.WhenAny(connectionTask, timeoutTask) == timeoutTask)
{
    throw new TimeoutException("Failed to connect within 30 seconds");
}

Console.WriteLine("Successfully connected to Rship server");

🛠️ Error Handling

Exception Types

The SDK throws standard .NET exceptions in various scenarios:

  • InvalidOperationException: Connection issues or SDK not properly initialized
  • JsonException: JSON serialization/deserialization errors
  • TaskCanceledException: Operation timeout or cancellation
  • ArgumentException: Invalid parameters passed to methods

Comprehensive Error Handling

public async Task HandleActionsWithErrorHandling()
{
    try
    {
        // Add action with error handling
        await target.AddActionAsync(
            ActionArgs<RiskyOperation>.New("Risky Operation", "risky-op"),
            async (action, data) =>
            {
                try
                {
                    // Your risky operation here
                    await PerformRiskyOperation(data);
                }
                catch (Exception ex)
                {
                    // Log the error
                    Console.WriteLine($"Error in action {action.Name}: {ex.Message}");
                    
                    // Optionally emit error status
                    if (_errorEmitter != null)
                    {
                        await _errorEmitter.PulseAsync(new ErrorData
                        {
                            ActionId = action.Id,
                            Error = ex.Message,
                            Timestamp = DateTime.UtcNow
                        });
                    }
                }
            }
        );
    }
    catch (InvalidOperationException ex) when (ex.Message.Contains("connection"))
    {
        Console.WriteLine("Connection lost, attempting to reconnect...");
        await sdk.AwaitConnectionAsync();
    }
    catch (JsonException ex)
    {
        Console.WriteLine($"JSON serialization error: {ex.Message}");
    }
}

// Robust emitter with retry logic
public async Task EmitWithRetry<T>(EmitterProxy<T> emitter, T data, int maxRetries = 3)
{
    for (int attempt = 1; attempt <= maxRetries; attempt++)
    {
        try
        {
            await emitter.PulseAsync(data);
            return; // Success
        }
        catch (InvalidOperationException ex) when (attempt < maxRetries)
        {
            Console.WriteLine($"Emit failed (attempt {attempt}/{maxRetries}): {ex.Message}");
            await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt))); // Exponential backoff
        }
    }
    
    throw new InvalidOperationException($"Failed to emit after {maxRetries} attempts");
}

Graceful Shutdown

public class GracefulService
{
    private SdkClient? _sdk;
    private CancellationTokenSource _cancellationTokenSource = new();

    public async Task StartAsync()
    {
        _sdk = SdkClient.Init();
        
        // Handle shutdown signals
        Console.CancelKeyPress += (_, e) =>
        {
            e.Cancel = true;
            _cancellationTokenSource.Cancel();
        };

        try
        {
            await RunServiceAsync(_cancellationTokenSource.Token);
        }
        catch (OperationCanceledException)
        {
            Console.WriteLine("Service is shutting down gracefully...");
        }
        finally
        {
            _sdk?.Dispose();
        }
    }

    private async Task RunServiceAsync(CancellationToken cancellationToken)
    {
        // Your service logic here
        while (!cancellationToken.IsCancellationRequested)
        {
            // Perform work
            await Task.Delay(1000, cancellationToken);
        }
    }
}

Environment Variables

  • RSHIP_ADDRESS: The Rship server address (default: localhost)
  • RSHIP_PORT: The Rship server port (default: 8080)

Example using environment variables:

// Load from environment or use defaults
var address = Environment.GetEnvironmentVariable("RSHIP_ADDRESS") ?? "localhost";
var port = Environment.GetEnvironmentVariable("RSHIP_PORT") ?? "8080";
var url = $"ws://{address}:{port}/myko";

await sdk.SetAddressAsync(url);

🔧 Advanced Usage

Custom Logging

using Microsoft.Extensions.Logging;

var loggerFactory = LoggerFactory.Create(builder =>
{
    builder
        .AddConsole()
        .AddDebug() 
        .SetMinimumLevel(LogLevel.Debug);
});

var sdk = SdkClient.Init(loggerFactory.CreateLogger<SdkClient>());

Error Handling

try 
{
    await emitter.PulseAsync(data);
}
catch (InvalidOperationException ex)
{
    // Handle connection issues
    Console.WriteLine($"Connection error: {ex.Message}");
}

Connection Status Monitoring

// The SDK automatically handles reconnection
await sdk.AwaitConnectionAsync(); // Blocks until connected

JSON Schema Validation

The SDK automatically generates JSON schemas for your data types:

public class ComplexData
{
    [JsonPropertyName("id")]
    public required string Id { get; set; }
    
    [JsonPropertyName("values")]
    public List<double> Values { get; set; } = new();
    
    [JsonPropertyName("metadata")]
    public Dictionary<string, object> Metadata { get; set; } = new();
    
    [JsonPropertyName("timestamp")]
    public DateTime Timestamp { get; set; } = DateTime.UtcNow;
}

// Schema is automatically generated when adding actions/emitters
await target.AddActionAsync(
    ActionArgs<ComplexData>.New("Process Complex Data", "process-complex"),
    async (action, data) =>
    {
        // Data is automatically validated against the schema
        Console.WriteLine($"Processing {data.Values.Count} values for ID: {data.Id}");
    }
);
// Models/VideoModels.cs
namespace MyRshipApp.Models;

public class PlayVideoRequest
{
    [JsonPropertyName("path")]
    public required string Path { get; set; }
    
    [JsonPropertyName("volume")]
    public double Volume { get; set; } = 1.0;
}

public class VideoStatusUpdate
{
    [JsonPropertyName("status")]
    public required string Status { get; set; }
    
    [JsonPropertyName("progress")]
    public double Progress { get; set; }
}

// Services/VideoService.cs
namespace MyRshipApp.Services;

public class VideoService
{
    private readonly ILogger<VideoService> _logger;
    
    public VideoService(ILogger<VideoService> logger)
    {
        _logger = logger;
    }
    
    public async Task PlayVideoAsync(string path, double volume)
    {
        _logger.LogInformation("Playing video: {Path} at volume {Volume}", path, volume);
        // Your video playback logic
        await Task.CompletedTask;
    }
}

// Handlers/VideoHandlers.cs
namespace MyRshipApp.Handlers;

public class VideoHandlers
{
    private readonly VideoService _videoService;
    private readonly EmitterProxy<VideoStatusUpdate> _statusEmitter;
    
    public VideoHandlers(VideoService videoService, EmitterProxy<VideoStatusUpdate> statusEmitter)
    {
        _videoService = videoService;
        _statusEmitter = statusEmitter;
    }
    
    public async Task HandlePlayVideo(RshipSdk.Entities.Action action, PlayVideoRequest request)
    {
        await _videoService.PlayVideoAsync(request.Path, request.Volume);
        
        await _statusEmitter.PulseAsync(new VideoStatusUpdate
        {
            Status = "playing",
            Progress = 0.0
        });
    }
}

Running Examples

cd Examples
dotnet restore
dotnet run

Dependencies

  • .NET 8.0+
  • System.Text.Json
  • Microsoft.Extensions.Logging.Abstractions
  • Websocket.Client
  • NJsonSchema

License

AGPL-3.0-or-later

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 was computed.  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 was computed.  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
1.0.19 133 6/30/2025
1.0.18 131 6/30/2025
1.0.17 136 6/30/2025
1.0.16 129 6/30/2025
1.0.15 131 6/30/2025
1.0.14 130 6/30/2025
1.0.13 135 6/30/2025
1.0.11 67 6/27/2025
1.0.10 71 6/27/2025
1.0.9 134 6/25/2025
1.0.8 134 6/24/2025
1.0.7 135 6/24/2025
1.0.5 134 6/24/2025
1.0.4 131 6/24/2025
1.0.3 134 6/24/2025
1.0.0 136 6/23/2025