SiLA2.Client 10.2.4

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

SiLA2.Client

Client-Side gRPC Communication and Server Discovery for SiLA2 Applications

NuGet Package SiLA2.Client on NuGet.org
Repository https://gitlab.com/SiLA2/sila_csharp
SiLA Standard https://sila-standard.com
License MIT

Overview

SiLA2.Client is a foundational module of the sila_csharp implementation that provides essential client-side utilities for connecting to and communicating with SiLA2 servers. It combines server discovery via mDNS, gRPC channel management, and dependency injection into a unified client configuration framework.

Key Capabilities

This module provides four core capabilities for building SiLA2 client applications:

  1. Server Discovery - Automatic detection of SiLA2 servers on the local network using mDNS/DNS-SD
  2. gRPC Channel Management - Creating and configuring secure gRPC channels with TLS/encryption
  3. Dependency Injection Setup - Simplified DI container configuration for client applications
  4. Configuration Management - Command-line argument parsing and configuration loading

When to Use This Module

SiLA2.Client is the standard choice for building SiLA2 client applications that connect to servers using compile-time generated gRPC stubs.

Scenario Use SiLA2.Client Use SiLA2.Client.Dynamic
Building client for known features ✅ Yes (compile-time stubs) ❌ No (unnecessary overhead)
Type-safe feature access ✅ Yes (IntelliSense support) ⚠️ Limited (reflection-based)
Performance-critical applications ✅ Yes (compiled code) ⚠️ Slower (runtime generation)
Automatic server discovery needed ✅ Yes (mDNS built-in) ✅ Yes (via SiLA2.Client)
Universal client (any feature) ❌ No ✅ Yes (runtime discovery)
Testing tools for unknown features ❌ No ✅ Yes

Choose SiLA2.Client when:

  • You know which SiLA2 features you'll communicate with at compile time
  • You want optimal performance and type safety
  • You're building production client applications
  • You need mDNS server discovery on the local network

Choose SiLA2.Client.Dynamic when:

  • You need to connect to any SiLA2 server without pre-compiling feature definitions
  • You're building universal tools or testing utilities
  • Runtime feature discovery is more important than performance

Installation

Install via NuGet Package Manager:

dotnet add package SiLA2.Client

Or via Package Manager Console:

Install-Package SiLA2.Client

Requirements

  • .NET 10.0+ / .NET Standard 2.0
  • SiLA2.Core (automatically installed as dependency)
  • Microsoft.Extensions.DependencyInjection (automatically installed)
  • Microsoft.Extensions.Logging (automatically installed)

Platform Compatibility

Target Framework Supported
.NET 10.0+ Yes (full feature support)
.NET Standard 2.0 Yes
.NET Framework 4.6.1+ Yes (via netstandard2.0)
.NET Core 2.0+ Yes (via netstandard2.0)
Mono 5.4+ Yes (via netstandard2.0)
Xamarin Yes (via netstandard2.0)

This package multi-targets net10.0 and netstandard2.0, allowing it to be used from both modern .NET and legacy .NET Framework projects.

Quick Start

Get connected to a SiLA2 server in 5 minutes.

1. Create a Console Application

dotnet new console -n MySiLA2Client
cd MySiLA2Client
dotnet add package SiLA2.Client
dotnet add package Microsoft.Extensions.Configuration.Json

2. Add Configuration File

Create appsettings.json:

{
  "ClientConfig": {
    "IpOrCdirOrFullyQualifiedHostName": "localhost",
    "Port": 50051,
    "DiscoveryServiceName": "_sila._tcp.local.",
    "NetworkInterface": "0.0.0.0"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information"
    }
  }
}

3. Discover Servers and Connect

using Microsoft.Extensions.Configuration;
using SiLA2.Client;
using System;
using System.Linq;
using System.Threading.Tasks;

class Program
{
    static async Task Main(string[] args)
    {
        // Load configuration
        var configuration = new ConfigurationBuilder()
            .AddJsonFile("appsettings.json", optional: false)
            .Build();

        // Initialize configurator
        var configurator = new Configurator(configuration, args);

        // Discover servers on the network
        Console.WriteLine("Searching for SiLA2 servers...");
        var servers = await configurator.SearchForServers();

        if (servers.Count == 0)
        {
            Console.WriteLine("No servers found.");
            return;
        }

        // Display discovered servers
        foreach (var server in servers.Values)
        {
            Console.WriteLine($"Found: {server.ServerName} at {server.Address}:{server.Port}");
        }

        // Connect to first server
        var targetServer = servers.Values.First();
        var channel = await configurator.GetChannel(
            targetServer.Address,
            targetServer.Port,
            acceptAnyServerCertificate: true);

        Console.WriteLine($"Connected to {targetServer.ServerName}");

        // Use the channel to create gRPC clients
        // var client = new MyFeature.MyFeatureClient(channel);

        await channel.ShutdownAsync();
    }
}

4. Run the Application

dotnet run

That's it! You've discovered and connected to a SiLA2 server.

Core Concepts

1. Server Discovery via mDNS

SiLA2 servers announce themselves on the network using mDNS (Multicast DNS) and DNS-SD (DNS Service Discovery). This allows clients to automatically find servers without manual configuration.

How mDNS Discovery Works
  1. Server Announcement: SiLA2 servers broadcast their presence on the network via mDNS
    • Service type: _sila._tcp.local.
    • Includes: hostname, port, server name, UUID, features
  2. Client Search: Clients query for SiLA2 services on the network
  3. Response: Servers respond with connection information
  4. Connection: Clients use the discovered information to create gRPC channels
Benefits of mDNS Discovery
  • Zero Configuration: No manual IP address entry required
  • Dynamic Networks: Servers can move between networks or change IPs
  • Service Identification: Servers advertise their implemented features
  • Laboratory Automation: Essential for plug-and-play lab instruments

2. Configurator - The Client Entry Point

The Configurator class is the primary entry point for setting up SiLA2 client applications. It performs three key functions:

  1. Dependency Injection Setup: Configures essential services (logging, network, gRPC)
  2. Server Discovery: Uses mDNS to find SiLA2 servers on the network
  3. Channel Creation: Creates configured gRPC channels for server communication

Key Services Registered by Configurator:

  • IServiceFinder - mDNS server discovery
  • IGrpcChannelProvider - gRPC channel factory
  • INetworkService - Network utilities
  • IClientConfig - Client configuration from appsettings.json

3. gRPC Channel Management

gRPC channels are the communication pathways between clients and servers. SiLA2 uses HTTP/2 and TLS encryption for secure, efficient communication.

Channel Configuration Options
Option Description Default
Host/Port Server address and port From configuration
TLS/SSL Encryption enabled Yes (HTTPS)
Certificate Validation Verify server certificates acceptAnyServerCertificate=true (dev mode)
Custom CA Custom certificate authority None (system CAs)

Production Best Practice: Always use acceptAnyServerCertificate=false with proper certificate infrastructure.

4. Connection Workflow

Typical Client Connection Flow:

1. Initialize Configurator
   ↓
2. Load Configuration (appsettings.json + command-line args)
   ↓
3. Search for Servers (mDNS discovery)
   ↓
4. Select Target Server
   ↓
5. Create gRPC Channel
   ↓
6. Instantiate Feature Client Stubs
   ↓
7. Invoke Commands/Properties
   ↓
8. Shutdown Channel

5. Command-Line Arguments

The Configurator automatically parses command-line arguments to override configuration:

# Override server host and port
dotnet run --host 192.168.1.100 --port 50052

# Override discovery settings
dotnet run --discovery-service _sila._tcp.local. --network-interface 192.168.1.0

Supported Arguments:

  • --host or -h - Server hostname or IP
  • --port or -p - Server port
  • --discovery-service - mDNS service name
  • --network-interface - Network interface for discovery

6. Binary Transfer Support

For large data transfers (files, images, AnIML documents), use the BinaryClientService:

var binaryUploadClient = new BinaryUpload.BinaryUploadClient(channel);
var binaryDownloadClient = new BinaryDownload.BinaryDownloadClient(channel);
var binaryService = new BinaryClientService(
    binaryUploadClient,
    binaryDownloadClient,
    logger);

// Upload binary data
byte[] data = File.ReadAllBytes("experiment_data.png");
string uuid = await binaryService.UploadBinary(
    data,
    chunkSize: 1024 * 1024,  // 1 MB chunks
    parameterIdentifier: "org.example.feature/Command/Parameter");

// Download binary data
byte[] downloadedData = await binaryService.DownloadBinary(uuid, chunkSize: 1024 * 1024);

Architecture & Components

Component Overview

┌─────────────────────────────────────────────────────────────┐
│                  SiLA2 Client Application                   │
│  (Console app, Desktop app, Web service)                    │
└───────────────────────────┬─────────────────────────────────┘
                            │
                            ▼
┌─────────────────────────────────────────────────────────────┐
│                     Configurator                            │
│  - Initializes DI container                                 │
│  - Discovers servers via mDNS                               │
│  - Creates gRPC channels                                    │
│  - Parses command-line arguments                            │
└───────────────────────────┬─────────────────────────────────┘
                            │
            ┌───────────────┴───────────────┐
            ▼                               ▼
┌─────────────────────┐         ┌─────────────────────┐
│   IServiceFinder    │         │ IGrpcChannelProvider│
│                     │         │                     │
│ - mDNS discovery    │         │ - Channel factory   │
│ - Returns server    │         │ - TLS configuration │
│   connection info   │         │ - Certificate       │
└─────────────────────┘         │   handling          │
                                └──────────┬──────────┘
                                           │
                                           ▼
                            ┌─────────────────────────┐
                            │   GrpcChannel           │
                            │   (to SiLA2 Server)     │
                            └──────────┬──────────────┘
                                       │
                                       ▼
                        ┌─────────────────────────────────┐
                        │   Feature Client Stubs          │
                        │   (Generated from .proto)       │
                        │   - MyFeature.MyFeatureClient   │
                        └─────────────────────────────────┘

Configurator

Purpose: Central configuration and initialization for SiLA2 clients.

Key Responsibilities:

  • Dependency Injection: Sets up service container with essential services
  • Configuration Loading: Reads appsettings.json and command-line arguments
  • Server Discovery: Searches for SiLA2 servers via mDNS
  • Channel Creation: Creates secure gRPC channels with TLS

Properties:

Property Type Description
Container IServiceCollection DI container for registering services
ServiceProvider IServiceProvider Built service provider with registered services
DiscoveredServers IDictionary<Guid, ConnectionInfo> Servers found via mDNS discovery

Key Methods:

// Search for servers on the network
Task<IDictionary<Guid, ConnectionInfo>> SearchForServers();

// Create channel using client configuration
Task<GrpcChannel> GetChannel(bool acceptAnyServerCertificate = true);

// Create channel to specific server
Task<GrpcChannel> GetChannel(string host, int port, bool acceptAnyServerCertificate = true, X509Certificate2 ca = null);

// Rebuild service provider after adding services
void UpdateServiceProvider();

IServiceFinder

Purpose: Discovers SiLA2 servers using mDNS/DNS-SD.

Key Method:

Task<IEnumerable<ConnectionInfo>> GetConnections(
    string serviceName,      // "_sila._tcp.local."
    string networkInterface, // "0.0.0.0" for all interfaces
    int timeout);            // Search duration in milliseconds

ConnectionInfo Structure:

public class ConnectionInfo
{
    public string Address { get; set; }           // Hostname or IP
    public int Port { get; set; }                 // gRPC port
    public string ServerName { get; set; }        // Display name
    public string ServerUuid { get; set; }        // Unique identifier
    public string ServerType { get; set; }        // Server type identifier
    public string ServerInfo { get; set; }        // Additional metadata
    public SilaCA SilaCA { get; set; }           // Certificate authority
}

IGrpcChannelProvider

Purpose: Creates and configures gRPC channels for server communication.

Key Method:

Task<GrpcChannel> GetChannel(
    string host,
    int port,
    bool acceptAnyServerCertificate = true,
    X509Certificate2 ca = null);

Channel Configuration:

  • Protocol: HTTPS (HTTP/2 over TLS)
  • Max Message Size: Configured for SiLA2 (default gRPC limits apply)
  • Keep-Alive: Configured for long-running connections
  • Certificate Validation: Configurable via acceptAnyServerCertificate

BinaryClientService

Purpose: Handles large binary data transfers using chunked streaming.

Key Methods:

// Upload binary data in chunks
Task<string> UploadBinary(byte[] value, int chunkSize, string parameterIdentifier);

// Download binary data in chunks
Task<byte[]> DownloadBinary(string binaryTransferUuid, int chunkSize);

How Chunked Transfer Works:

  1. Upload:

    • Client creates binary transfer on server
    • Splits data into chunks (e.g., 1 MB each)
    • Uploads chunks via bidirectional streaming
    • Server acknowledges each chunk
    • Returns UUID for referencing uploaded data
  2. Download:

    • Client requests binary metadata (size, chunk count)
    • Requests chunks sequentially
    • Server streams chunks back
    • Client assembles chunks into complete byte array

Use Case: Transferring AnIML documents, images, or large datasets.

Usage Examples

Example 1: Basic Server Discovery and Connection

using Microsoft.Extensions.Configuration;
using SiLA2.Client;
using System;
using System.Linq;
using System.Threading.Tasks;

public class BasicClient
{
    public static async Task Main(string[] args)
    {
        // Setup configuration
        var configuration = new ConfigurationBuilder()
            .AddJsonFile("appsettings.json")
            .Build();

        // Initialize configurator
        var configurator = new Configurator(configuration, args);

        // Discover servers
        var servers = await configurator.SearchForServers();

        if (servers.Count == 0)
        {
            Console.WriteLine("No SiLA2 servers found on the network.");
            return;
        }

        // Display all discovered servers
        Console.WriteLine($"Found {servers.Count} server(s):");
        foreach (var server in servers.Values)
        {
            Console.WriteLine($"  - {server.ServerName} ({server.ServerType})");
            Console.WriteLine($"    Address: {server.Address}:{server.Port}");
            Console.WriteLine($"    UUID: {server.ServerUuid}");
        }

        // Connect to first server
        var targetServer = servers.Values.First();
        var channel = await configurator.GetChannel(
            targetServer.Address,
            targetServer.Port,
            acceptAnyServerCertificate: true);

        Console.WriteLine($"Connected to {targetServer.ServerName}");

        // Create feature client stubs here
        // var client = new TemperatureController.TemperatureControllerClient(channel);

        // Cleanup
        await channel.ShutdownAsync();
    }
}

Example 2: Filtering Discovered Servers

using SiLA2.Client;
using System;
using System.Linq;
using System.Threading.Tasks;

public class FilteredDiscovery
{
    public static async Task<GrpcChannel> ConnectToTemperatureServer(Configurator configurator)
    {
        // Discover all servers
        var servers = await configurator.SearchForServers();

        // Filter by server type
        var tempServer = servers.Values
            .FirstOrDefault(s => s.ServerType == "SiLA2TemperatureServer");

        if (tempServer == null)
        {
            throw new Exception("Temperature server not found on network");
        }

        Console.WriteLine($"Found Temperature Server: {tempServer.ServerName}");

        // Connect with server's own certificate authority
        return await configurator.GetChannel(
            tempServer.Address,
            tempServer.Port,
            acceptAnyServerCertificate: false,
            ca: tempServer.SilaCA.GetCaFromFormattedCa());
    }
}

Example 3: Manual Server Connection (No Discovery)

using SiLA2.Client;
using System.Threading.Tasks;

public class ManualConnection
{
    public static async Task<GrpcChannel> ConnectManually()
    {
        var configuration = new ConfigurationBuilder()
            .AddJsonFile("appsettings.json")
            .Build();

        var configurator = new Configurator(configuration, new string[] { });

        // Connect directly to known server without mDNS
        var channel = await configurator.GetChannel(
            host: "192.168.1.100",
            port: 50051,
            acceptAnyServerCertificate: true);

        Console.WriteLine("Connected to server at 192.168.1.100:50051");

        return channel;
    }
}

Example 4: Calling Unobservable Commands

using Sila2.Org.Silastandard.Protobuf;
using TemperatureController = Sila2.Org.Silastandard.Examples.TemperatureController.V1;
using System.Threading.Tasks;

public class UnobservableCommandExample
{
    public static async Task SetTemperature(GrpcChannel channel, double temperatureKelvin)
    {
        // Create client stub
        var client = new TemperatureController.TemperatureController.TemperatureControllerClient(channel);

        // Prepare request
        var request = new TemperatureController.SetTargetTemperature_Parameters
        {
            Temperature = new Real { Value = temperatureKelvin }
        };

        // Call command (returns immediately)
        var response = await client.SetTargetTemperatureAsync(request);

        Console.WriteLine("Target temperature set successfully");
    }
}

Example 5: Calling Observable Commands

using Sila2.Org.Silastandard;
using Sila2.Org.Silastandard.Protobuf;
using TemperatureController = Sila2.Org.Silastandard.Examples.TemperatureController.V1;
using System;
using System.Threading;
using System.Threading.Tasks;

public class ObservableCommandExample
{
    public static async Task ControlTemperature(GrpcChannel channel, double targetTemp)
    {
        var client = new TemperatureController.TemperatureController.TemperatureControllerClient(channel);

        // 1. Initiate observable command
        var request = new TemperatureController.ControlTemperature_Parameters
        {
            Temperature = new Real { Value = targetTemp }
        };

        var confirmation = await client.ControlTemperatureAsync(request);
        var commandUuid = confirmation.CommandExecutionUUID;

        Console.WriteLine($"Command initiated: {commandUuid.Value}");

        // 2. Subscribe to ExecutionInfo (progress updates)
        var infoRequest = new Subscribe_Parameters
        {
            CommandExecutionUUID = commandUuid
        };

        using var infoStream = client.ControlTemperature_Info(infoRequest);
        var cancellationToken = new CancellationTokenSource(TimeSpan.FromMinutes(5)).Token;

        await foreach (var executionInfo in infoStream.ResponseStream.ReadAllAsync(cancellationToken))
        {
            Console.WriteLine($"Status: {executionInfo.CommandStatus}");
            Console.WriteLine($"Progress: {executionInfo.ProgressInfo?.Value * 100:F1}%");

            if (executionInfo.CommandStatus == ExecutionInfo.Types.CommandStatus.FinishedSuccessfully)
            {
                Console.WriteLine("Command completed successfully!");
                break;
            }
            else if (executionInfo.CommandStatus == ExecutionInfo.Types.CommandStatus.FinishedWithError)
            {
                Console.WriteLine($"Command failed: {executionInfo.Message?.Value}");
                throw new Exception("Command execution failed");
            }
        }

        // 3. Retrieve final result
        var resultRequest = new CommandExecutionUUID { Value = commandUuid.Value };
        var result = await client.ControlTemperature_ResultAsync(resultRequest);

        Console.WriteLine("Temperature control completed");
    }
}

Example 6: Reading Unobservable Properties

using TemperatureController = Sila2.Org.Silastandard.Examples.TemperatureController.V1;
using System;
using System.Threading.Tasks;

public class UnobservablePropertyExample
{
    public static async Task ReadTemperatureRange(GrpcChannel channel)
    {
        var client = new TemperatureController.TemperatureController.TemperatureControllerClient(channel);

        // Read property (returns immediately)
        var response = await client.Get_TemperatureRangeAsync(new Google.Protobuf.WellKnownTypes.Empty());

        Console.WriteLine($"Min Temperature: {response.MinTemperature.Value} K");
        Console.WriteLine($"Max Temperature: {response.MaxTemperature.Value} K");
    }
}

Example 7: Subscribing to Observable Properties

using TemperatureController = Sila2.Org.Silastandard.Examples.TemperatureController.V1;
using System;
using System.Threading;
using System.Threading.Tasks;

public class ObservablePropertyExample
{
    public static async Task MonitorCurrentTemperature(GrpcChannel channel, TimeSpan duration)
    {
        var client = new TemperatureController.TemperatureController.TemperatureControllerClient(channel);

        // Subscribe to property updates
        var request = new Google.Protobuf.WellKnownTypes.Empty();
        using var stream = client.Subscribe_CurrentTemperature(request);

        var cancellationToken = new CancellationTokenSource(duration).Token;

        Console.WriteLine($"Monitoring temperature for {duration.TotalSeconds} seconds...");

        try
        {
            await foreach (var update in stream.ResponseStream.ReadAllAsync(cancellationToken))
            {
                Console.WriteLine($"Current Temperature: {update.CurrentTemperature.Value} K");
            }
        }
        catch (OperationCanceledException)
        {
            Console.WriteLine("Monitoring stopped (timeout reached)");
        }
    }
}

Example 8: Working with Metadata

using Grpc.Core;
using Sila2.Org.Silastandard.Protobuf;
using System;
using System.Text;
using System.Threading.Tasks;

public class MetadataExample
{
    public static async Task CallWithMetadata(GrpcChannel channel)
    {
        var client = new MyFeature.MyFeatureClient(channel);

        // Prepare metadata
        var metadata = new Metadata();
        metadata.Add("client-id", "my-client-app");
        metadata.Add("user", Convert.ToBase64String(Encoding.UTF8.GetBytes("john.doe")));
        metadata.Add("session", Guid.NewGuid().ToString());

        // Call with metadata
        var request = new MyCommand_Parameters
        {
            Parameter1 = new String { Value = "test" }
        };

        var response = await client.MyCommandAsync(request, metadata);

        // Extract response metadata
        var responseHeaders = response.GetTrailers();
        if (responseHeaders != null)
        {
            foreach (var entry in responseHeaders)
            {
                Console.WriteLine($"Response metadata: {entry.Key} = {entry.Value}");
            }
        }
    }
}

Example 9: Binary Upload

using SiLA2.Client;
using System;
using System.IO;
using System.Threading.Tasks;

public class BinaryUploadExample
{
    public static async Task UploadFile(GrpcChannel channel, string filePath)
    {
        // Create binary service
        var binaryUploadClient = new Sila2.Org.Silastandard.BinaryUpload.BinaryUploadClient(channel);
        var binaryDownloadClient = new Sila2.Org.Silastandard.BinaryDownload.BinaryDownloadClient(channel);
        var binaryService = new BinaryClientService(
            binaryUploadClient,
            binaryDownloadClient,
            logger);

        // Read file
        byte[] fileData = await File.ReadAllBytesAsync(filePath);
        Console.WriteLine($"Uploading {fileData.Length} bytes...");

        // Upload in 1 MB chunks
        string transferUuid = await binaryService.UploadBinary(
            value: fileData,
            chunkSize: 1024 * 1024,
            parameterIdentifier: "org.example.feature/UploadData/FileData");

        Console.WriteLine($"Upload complete. Binary UUID: {transferUuid}");

        // Use the UUID in a command parameter
        // var request = new UploadData_Parameters
        // {
        //     FileData = new Binary { BinaryTransferUUID = transferUuid }
        // };
    }
}

Example 10: Complete Temperature Client Example

Based on src/Examples/TemperatureController/SiLA2.Temperature.Client.App/Program.cs:

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using SiLA2.Client;
using Serilog;
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using TemperatureController = Sila2.Org.Silastandard.Examples.TemperatureController.V1;

class Program
{
    static async Task Main(string[] args)
    {
        // Load configuration
        var configuration = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
            .Build();

        // Initialize configurator
        var configurator = new Configurator(configuration, args);

        // Setup logging
        Log.Logger = new LoggerConfiguration()
            .ReadFrom.Configuration(configuration)
            .CreateLogger();

        configurator.Container.AddLogging(x =>
        {
            x.ClearProviders();
            x.AddSerilog(dispose: true);
        });
        configurator.UpdateServiceProvider();

        var logger = configurator.ServiceProvider.GetRequiredService<ILogger<Program>>();

        // Discover servers
        logger.LogInformation("Starting Server Discovery...");
        var serverMap = await configurator.SearchForServers();

        // Connect to Temperature server
        GrpcChannel channel;
        var serverType = "SiLA2TemperatureServer";
        var server = serverMap.Values.FirstOrDefault(x => x.ServerType == serverType);

        if (server != null)
        {
            logger.LogInformation("Found Server");
            logger.LogInformation(server.ServerInfo);
            logger.LogInformation($"Connecting to {server}");

            channel = await configurator.GetChannel(
                server.Address,
                server.Port,
                acceptAnyServerCertificate: false,
                ca: server.SilaCA.GetCaFromFormattedCa());
        }
        else
        {
            logger.LogInformation("No server discovered. Using configuration fallback.");
            channel = await configurator.GetChannel(acceptAnyServerCertificate: true);
        }

        // Create client
        logger.LogInformation("Initializing Client...");
        var client = new TemperatureController.TemperatureController.TemperatureControllerClient(channel);

        // Call commands and properties
        // ... (see full example in repository)

        // Cleanup
        logger.LogInformation("Shutting down connection...");
        await channel.ShutdownAsync();

        Console.WriteLine("Press any key to exit...");
        Console.ReadKey();
    }
}

Configuration

appsettings.json Configuration

Complete Example:

{
  "ClientConfig": {
    "IpOrCdirOrFullyQualifiedHostName": "localhost",
    "Port": 50051,
    "DiscoveryServiceName": "_sila._tcp.local.",
    "NetworkInterface": "0.0.0.0",
    "Timeout": 30000
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "SiLA2.Client": "Debug",
      "Grpc": "Warning"
    }
  }
}

Configuration Properties:

Property Type Default Description
IpOrCdirOrFullyQualifiedHostName string "localhost" Server hostname or IP address
Port int 50051 Server gRPC port
DiscoveryServiceName string "_sila._tcp.local." mDNS service name for discovery
NetworkInterface string "0.0.0.0" Network interface for mDNS (0.0.0.0 = all)
Timeout int 30000 Connection timeout in milliseconds

Command-Line Arguments

Override configuration via command-line:

# Override server connection
dotnet run --host 192.168.1.100 --port 50052

# Override discovery settings
dotnet run --discovery-service _sila._tcp.local. --network-interface 192.168.1.0

# Combine multiple arguments
dotnet run --host localhost --port 50051 --discovery-service _sila._tcp.local.

Argument Precedence: Command-line args > appsettings.json > defaults

Dependency Injection Setup

Minimal Setup:

var configuration = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json")
    .Build();

var configurator = new Configurator(configuration, args);

// Services are auto-registered:
// - IServiceFinder
// - IGrpcChannelProvider
// - INetworkService
// - IClientConfig

Custom Services:

var configurator = new Configurator(configuration, args);

// Add custom services
configurator.Container.AddSingleton<IMyService, MyService>();
configurator.Container.AddScoped<IRepository, Repository>();

// Rebuild service provider
configurator.UpdateServiceProvider();

// Access services
var myService = configurator.ServiceProvider.GetRequiredService<IMyService>();

Security & Certificates

TLS/SSL Configuration

SiLA2 uses HTTPS (HTTP/2 over TLS) for all gRPC communication.

Development Mode (Self-Signed Certificates)
// Accept any server certificate (DEVELOPMENT ONLY)
var channel = await configurator.GetChannel(
    host: "localhost",
    port: 50051,
    acceptAnyServerCertificate: true);  // ⚠️ Insecure - dev only

Security Warning: This disables certificate validation. Use only in development/testing.

Production Mode (Proper Certificates)
// Use server's own CA certificate
var channel = await configurator.GetChannel(
    server.Address,
    server.Port,
    acceptAnyServerCertificate: false,  // ✅ Validate certificates
    ca: server.SilaCA.GetCaFromFormattedCa());
Custom Certificate Authority
// Load custom CA certificate
var caCert = new X509Certificate2("path/to/ca-certificate.pem");

var channel = await configurator.GetChannel(
    "myserver.example.com",
    50051,
    acceptAnyServerCertificate: false,
    ca: caCert);

Certificate Validation Callbacks

For advanced certificate validation:

var httpHandler = new HttpClientHandler();
httpHandler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) =>
{
    // Custom validation logic
    if (errors == SslPolicyErrors.None)
        return true;

    // Log certificate details
    Console.WriteLine($"Certificate subject: {cert.Subject}");
    Console.WriteLine($"Certificate issuer: {cert.Issuer}");
    Console.WriteLine($"Errors: {errors}");

    // Accept specific certificate thumbprints
    var trustedThumbprints = new[] { "ABC123...", "DEF456..." };
    return trustedThumbprints.Contains(cert.GetCertHashString());
};

var channelOptions = new GrpcChannelOptions
{
    HttpHandler = httpHandler
};

var channel = GrpcChannel.ForAddress("https://myserver:50051", channelOptions);

Production Certificate Setup

Recommended Approach:

  1. Use Valid SSL Certificates: Obtain certificates from a trusted CA (Let's Encrypt, commercial CA)
  2. Enable Certificate Validation: Set acceptAnyServerCertificate=false
  3. Certificate Pinning (Optional): Validate specific certificate thumbprints
  4. Mutual TLS (Optional): Client certificates for authentication

Example Production Configuration:

var channel = await configurator.GetChannel(
    host: "sila-server.production.com",
    port: 443,  // Standard HTTPS port
    acceptAnyServerCertificate: false,
    ca: null);  // Use system-trusted CAs

Error Handling

gRPC Status Codes

Handle common gRPC errors:

using Grpc.Core;
using System;
using System.Threading.Tasks;

public class ErrorHandlingExample
{
    public static async Task CallCommandWithErrorHandling(GrpcChannel channel)
    {
        try
        {
            var client = new MyFeature.MyFeatureClient(channel);
            var response = await client.MyCommandAsync(request);
        }
        catch (RpcException ex) when (ex.StatusCode == StatusCode.Unavailable)
        {
            Console.WriteLine("Server is unavailable. Check network connection.");
        }
        catch (RpcException ex) when (ex.StatusCode == StatusCode.DeadlineExceeded)
        {
            Console.WriteLine("Request timed out. Server may be overloaded.");
        }
        catch (RpcException ex) when (ex.StatusCode == StatusCode.Unauthenticated)
        {
            Console.WriteLine("Authentication failed. Check credentials.");
        }
        catch (RpcException ex) when (ex.StatusCode == StatusCode.PermissionDenied)
        {
            Console.WriteLine("Access denied. Insufficient permissions.");
        }
        catch (RpcException ex) when (ex.StatusCode == StatusCode.InvalidArgument)
        {
            Console.WriteLine($"Invalid parameter: {ex.Status.Detail}");
        }
        catch (RpcException ex) when (ex.StatusCode == StatusCode.FailedPrecondition)
        {
            // SiLA2 defined execution error
            Console.WriteLine($"Command execution error: {ex.Status.Detail}");

            // Parse SiLA2 error metadata
            var errorType = ex.Trailers.GetValue("sila2-error-type");
            var errorIdentifier = ex.Trailers.GetValue("sila2-error-identifier");
            Console.WriteLine($"Error type: {errorType}");
            Console.WriteLine($"Error ID: {errorIdentifier}");
        }
        catch (RpcException ex)
        {
            Console.WriteLine($"gRPC error: {ex.StatusCode} - {ex.Status.Detail}");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Unexpected error: {ex.Message}");
        }
    }
}

Network Error Handling

using System;
using System.Net.Sockets;
using System.Threading.Tasks;

public class NetworkErrorExample
{
    public static async Task<GrpcChannel> ConnectWithRetry(Configurator configurator, int maxRetries = 3)
    {
        for (int attempt = 1; attempt <= maxRetries; attempt++)
        {
            try
            {
                Console.WriteLine($"Connection attempt {attempt}/{maxRetries}...");
                return await configurator.GetChannel(acceptAnyServerCertificate: true);
            }
            catch (SocketException ex)
            {
                Console.WriteLine($"Network error: {ex.Message}");

                if (attempt == maxRetries)
                    throw;

                await Task.Delay(TimeSpan.FromSeconds(2 * attempt)); // Exponential backoff
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Unexpected error: {ex.Message}");
                throw;
            }
        }

        throw new Exception("Failed to connect after maximum retries");
    }
}

Timeout Handling

using Grpc.Core;
using System;
using System.Threading;
using System.Threading.Tasks;

public class TimeoutExample
{
    public static async Task CallWithTimeout(GrpcChannel channel, TimeSpan timeout)
    {
        var client = new MyFeature.MyFeatureClient(channel);

        var cancellationToken = new CancellationTokenSource(timeout).Token;
        var deadline = DateTime.UtcNow.Add(timeout);

        try
        {
            var callOptions = new CallOptions(
                deadline: deadline,
                cancellationToken: cancellationToken);

            var response = await client.MyCommandAsync(request, callOptions);
        }
        catch (RpcException ex) when (ex.StatusCode == StatusCode.DeadlineExceeded)
        {
            Console.WriteLine($"Operation timed out after {timeout.TotalSeconds} seconds");
        }
        catch (OperationCanceledException)
        {
            Console.WriteLine("Operation was cancelled");
        }
    }
}

Retry Strategies

using Polly;
using System;
using System.Threading.Tasks;

public class RetryExample
{
    public static async Task CallWithRetry(GrpcChannel channel)
    {
        var retryPolicy = Policy
            .Handle<RpcException>(ex => ex.StatusCode == StatusCode.Unavailable)
            .WaitAndRetryAsync(
                retryCount: 3,
                sleepDurationProvider: attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt)),
                onRetry: (exception, timespan, attempt, context) =>
                {
                    Console.WriteLine($"Retry {attempt} after {timespan.TotalSeconds}s due to: {exception.Message}");
                });

        await retryPolicy.ExecuteAsync(async () =>
        {
            var client = new MyFeature.MyFeatureClient(channel);
            var response = await client.MyCommandAsync(request);
        });
    }
}

Advanced Topics

Custom DI Service Registration

using Microsoft.Extensions.DependencyInjection;
using SiLA2.Client;

public class CustomDIExample
{
    public static void ConfigureServices(Configurator configurator)
    {
        // Add custom singleton services
        configurator.Container.AddSingleton<IDeviceManager, DeviceManager>();
        configurator.Container.AddSingleton<IDataLogger, FileDataLogger>();

        // Add scoped services (per-request lifetime)
        configurator.Container.AddScoped<IRepository, DatabaseRepository>();

        // Add HTTP client for external APIs
        configurator.Container.AddHttpClient<IExternalApi, ExternalApiClient>();

        // Add options pattern
        configurator.Container.Configure<MyOptions>(options =>
        {
            options.Setting1 = "value1";
            options.Setting2 = 42;
        });

        // Rebuild service provider
        configurator.UpdateServiceProvider();

        // Access services
        var deviceManager = configurator.ServiceProvider.GetRequiredService<IDeviceManager>();
    }
}

Multiple Server Connections

using SiLA2.Client;
using System.Collections.Generic;
using System.Threading.Tasks;

public class MultiServerExample
{
    public static async Task ConnectToMultipleServers(Configurator configurator)
    {
        var servers = await configurator.SearchForServers();

        var channels = new Dictionary<string, GrpcChannel>();

        foreach (var server in servers.Values)
        {
            var channel = await configurator.GetChannel(
                server.Address,
                server.Port,
                acceptAnyServerCertificate: true);

            channels[server.ServerName] = channel;
        }

        // Use channels
        foreach (var (name, channel) in channels)
        {
            Console.WriteLine($"Connected to {name}");

            // Create feature clients
            // var client = new MyFeature.MyFeatureClient(channel);
        }

        // Cleanup
        foreach (var channel in channels.Values)
        {
            await channel.ShutdownAsync();
        }
    }
}

Connection Pooling

using Grpc.Net.Client;
using System.Collections.Concurrent;
using System.Threading.Tasks;

public class ConnectionPool
{
    private readonly ConcurrentDictionary<string, GrpcChannel> _channels = new();
    private readonly Configurator _configurator;

    public ConnectionPool(Configurator configurator)
    {
        _configurator = configurator;
    }

    public async Task<GrpcChannel> GetOrCreateChannel(string host, int port)
    {
        var key = $"{host}:{port}";

        return _channels.GetOrAdd(key, async _ =>
        {
            return await _configurator.GetChannel(host, port, acceptAnyServerCertificate: true);
        }).Result;
    }

    public async Task CloseAll()
    {
        foreach (var channel in _channels.Values)
        {
            await channel.ShutdownAsync();
        }
        _channels.Clear();
    }
}

Health Checks

using Grpc.Core;
using Grpc.Health.V1;
using System;
using System.Threading.Tasks;

public class HealthCheckExample
{
    public static async Task<bool> CheckServerHealth(GrpcChannel channel)
    {
        try
        {
            var healthClient = new Health.HealthClient(channel);
            var response = await healthClient.CheckAsync(new HealthCheckRequest());

            return response.Status == HealthCheckResponse.Types.ServingStatus.Serving;
        }
        catch (RpcException ex)
        {
            Console.WriteLine($"Health check failed: {ex.Status.Detail}");
            return false;
        }
    }

    public static async Task MonitorServerHealth(GrpcChannel channel, TimeSpan interval)
    {
        var healthClient = new Health.HealthClient(channel);

        using var watchCall = healthClient.Watch(new HealthCheckRequest());

        await foreach (var response in watchCall.ResponseStream.ReadAllAsync())
        {
            Console.WriteLine($"Server health: {response.Status}");

            if (response.Status != HealthCheckResponse.Types.ServingStatus.Serving)
            {
                Console.WriteLine("Server is unhealthy!");
            }
        }
    }
}

Metadata Extraction

using Grpc.Core;
using System;
using System.Text;
using System.Threading.Tasks;

public class MetadataExtractor
{
    public static async Task ExtractMetadata(GrpcChannel channel)
    {
        var client = new MyFeature.MyFeatureClient(channel);

        // Create call with headers
        var headers = new Metadata();
        headers.Add("custom-header", "value");

        var call = client.MyCommandAsync(request, headers);

        // Get response headers (sent before response)
        var responseHeaders = await call.ResponseHeadersAsync;
        Console.WriteLine("Response Headers:");
        foreach (var header in responseHeaders)
        {
            Console.WriteLine($"  {header.Key}: {header.Value}");
        }

        // Get response
        var response = await call.ResponseAsync;

        // Get trailers (sent after response)
        var trailers = call.GetTrailers();
        Console.WriteLine("Response Trailers:");
        foreach (var trailer in trailers)
        {
            Console.WriteLine($"  {trailer.Key}: {trailer.Value}");
        }
    }
}

Working Examples

The repository includes complete working client examples demonstrating real-world usage patterns.

Temperature Controller Client

Location: src/Examples/TemperatureController/SiLA2.Temperature.Client.App/

Run:

dotnet run --project src/Examples/TemperatureController/SiLA2.Temperature.Client.App/SiLA2.Temperature.Client.App.csproj

Features Demonstrated:

  • Automatic server discovery via mDNS
  • Fallback to manual configuration
  • Certificate handling (server's own CA)
  • Calling unobservable commands (SetTargetTemperature)
  • Calling observable commands (ControlTemperature) with progress tracking
  • Subscribing to observable properties (CurrentTemperature)
  • Error handling (parameter validation, defined execution errors)

Shaker Controller Client

Location: src/Examples/ShakerController/SiLA2.Shaker.Client.App/

Run:

dotnet run --project src/Examples/ShakerController/SiLA2.Shaker.Client.App/SiLA2.Shaker.Client.App.csproj

Features Demonstrated:

  • Server type filtering (connecting to specific server type)
  • Reading unobservable properties (ClampState)
  • Calling unobservable commands (OpenClamp, CloseClamp)
  • Observable command execution (Shake)
  • SiLA2 validation error handling
  • Precondition checking (clamp must be closed to shake)

Authentication Client Example

Location: src/Examples/AuthenticationAuthorization/Auth.Client.App/

Features Demonstrated:

  • Authentication with SiLA2 servers
  • Bearer token management
  • Metadata for authentication headers
  • Role-based access control

Comparison: SiLA2.Client vs SiLA2.Client.Dynamic

When to Use SiLA2.Client (Compile-Time Stubs)

Use SiLA2.Client when:

  • You know which features you'll communicate with at development time
  • You want compile-time type checking and IntelliSense
  • Performance is critical (no runtime reflection overhead)
  • You're building production client applications
  • You need maximum type safety

Benefits:

  • Type Safety: Compile-time checking prevents errors
  • Performance: No runtime type generation overhead (~5x faster than dynamic)
  • IntelliSense: Full code completion for all features
  • Refactoring: Rename operations work correctly
  • Debugging: Standard debugging with breakpoints and watches

Drawbacks:

  • Recompilation: Must recompile when features change
  • Feature Coupling: Client code coupled to specific feature versions
  • Binary Size: Generated stubs increase assembly size

When to Use SiLA2.Client.Dynamic (Runtime Generation)

Use SiLA2.Client.Dynamic when:

  • You need to connect to any SiLA2 server without pre-compiling features
  • You're building universal testing tools or debugging utilities
  • Runtime feature discovery is more important than performance
  • You want to avoid managing feature assemblies

Benefits:

  • Flexibility: Works with any SiLA2 feature at runtime
  • No Recompilation: Load new features without rebuilding
  • Smaller Binaries: No generated code in assembly
  • Universal Tools: Single client for all servers

Drawbacks:

  • No Type Safety: Runtime errors instead of compile errors
  • Limited IntelliSense: Dynamic types don't provide code completion
  • Performance Overhead: Reflection-based calls (~5x slower)
  • Complex Debugging: Dynamic types harder to inspect

Performance Comparison

Operation SiLA2.Client SiLA2.Client.Dynamic Winner
Command call overhead ~0.5ms ~2-5ms ✅ SiLA2.Client (5-10x faster)
Property read overhead ~0.3ms ~1-3ms ✅ SiLA2.Client
Feature loading time Build-time ~50-200ms ✅ SiLA2.Client
Binary size Larger (+stubs) Smaller ✅ SiLA2.Client.Dynamic
Type safety Compile-time Runtime ✅ SiLA2.Client

Note: For most SiLA2 operations (which involve I/O and device communication), the overhead difference is negligible compared to the operation duration (seconds to minutes).

Hybrid Approach

Use both libraries in the same application:

// Use SiLA2.Client for known features (performance-critical)
var tempClient = new TemperatureController.TemperatureControllerClient(channel);
var temp = await tempClient.Get_CurrentTemperatureAsync(new Empty());

// Use SiLA2.Client.Dynamic for unknown features (flexibility)
var dynamicService = new DynamicMessageService(payloadFactory);
var unknownFeature = silaServer.ReadFeature("UnknownFeature-v1_0.sila.xml");
var result = dynamicService.GetUnobservableProperty("SomeProperty", channel, unknownFeature);

API Reference Summary

IConfigurator

public interface IConfigurator
{
    // Dependency injection
    IServiceCollection Container { get; }
    IServiceProvider ServiceProvider { get; }
    void UpdateServiceProvider();

    // Server discovery
    IDictionary<Guid, ConnectionInfo> DiscoveredServers { get; }
    Task<IDictionary<Guid, ConnectionInfo>> SearchForServers();

    // Channel creation
    Task<GrpcChannel> GetChannel(bool acceptAnyServerCertificate = true);
    Task<GrpcChannel> GetChannel(string host, int port, bool acceptAnyServerCertificate = true, X509Certificate2 ca = null);
}

Configurator

public class Configurator : IConfigurator
{
    public Configurator(IConfiguration configuration, string[] args);

    public IServiceCollection Container { get; }
    public IServiceProvider ServiceProvider { get; private set; }
    public IDictionary<Guid, ConnectionInfo> DiscoveredServers { get; }

    public Task<IDictionary<Guid, ConnectionInfo>> SearchForServers();
    public Task<GrpcChannel> GetChannel(bool acceptAnyServerCertificate = true);
    public Task<GrpcChannel> GetChannel(string host, int port, bool acceptAnyServerCertificate = true, X509Certificate2 ca = null);
    public void UpdateServiceProvider();
}

IBinaryClientService

public interface IBinaryClientService
{
    Task<string> UploadBinary(byte[] value, int chunkSize, string parameterIdentifier);
    Task<byte[]> DownloadBinary(string binaryTransferUuid, int chunkSize);
}

ConnectionInfo

public class ConnectionInfo
{
    public string Address { get; set; }
    public int Port { get; set; }
    public string ServerName { get; set; }
    public string ServerUuid { get; set; }
    public string ServerType { get; set; }
    public string ServerInfo { get; set; }
    public SilaCA SilaCA { get; set; }

    public override string ToString();
}

Core SiLA2 Packages:

  • SiLA2.Core - Core server implementation, domain models, network discovery (required dependency)
  • SiLA2.AspNetCore - ASP.NET Core integration for building SiLA2 servers
  • SiLA2.Utils - Network utilities, mDNS, configuration (included via SiLA2.Core)

Client Libraries:

Optional Modules:

Contributing & Development

This package is part of the sila_csharp project.

Building from Source

git clone --recurse-submodules https://gitlab.com/SiLA2/sila_csharp.git
cd sila_csharp/src
dotnet build SiLA2.Client/SiLA2.Client.csproj

Running Tests

# Run client integration tests
dotnet test Tests/SiLA2.Client.Tests/SiLA2.Client.Tests.csproj

# Run end-to-end tests (requires running server)
dotnet test Tests/SiLA2.IntegrationTests.Client.Tests/SiLA2.IntegrationTests.Client.Tests.csproj

Project Structure

SiLA2.Client/
├── Configurator.cs                 # Main client configuration class
├── IConfigurator.cs                # Configurator interface
├── BinaryClientService.cs          # Binary transfer implementation
├── IBinaryClientService.cs         # Binary transfer interface
├── README.md                       # This file
└── SiLA2.Client.csproj            # Project file

External Resources:

License

This project is licensed under the MIT License.

Maintainer

Christoph Pohl (@Chamundi)

Security

For security vulnerabilities, please refer to the SiLA2 Vulnerability Policy.


Questions or Issues?

  • Open an issue on GitLab
  • Join the SiLA community on Slack
  • Check the Wiki for additional documentation

Getting Started with SiLA2 Client Development?

  1. Install the package: dotnet add package SiLA2.Client
  2. Create configuration: Add appsettings.json with ClientConfig section
  3. Discover servers: Use Configurator.SearchForServers()
  4. Create channel: Use Configurator.GetChannel()
  5. Build feature clients: Reference feature assemblies and create gRPC client stubs
  6. Call commands/properties: Use generated client stub classes

Happy SiLA2 client development!

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  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 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. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on SiLA2.Client:

Package Downloads
SiLA2.Frontend.Razor

Web Frontend Extension for SiLA2.Server Package

SiLA2.Client.Dynamic

SiLA2.Client.Dynamic Package

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
10.2.4 169 3/13/2026
10.2.3 164 3/7/2026
10.2.2 363 2/12/2026
10.2.1 428 1/25/2026
10.2.0 816 12/23/2025
10.1.0 1,209 11/29/2025
10.0.0 1,751 11/11/2025
9.0.4 2,663 6/25/2025
9.0.3 2,229 6/21/2025
9.0.2 2,877 1/6/2025
9.0.1 2,320 11/17/2024
9.0.0 2,232 11/13/2024
8.1.2 2,464 10/20/2024
8.1.1 2,962 8/31/2024
8.1.0 3,212 2/11/2024
8.0.0 2,725 11/15/2023
7.5.4 4,149 10/27/2023
7.5.3 2,476 7/19/2023
7.5.2 2,375 7/3/2023
7.5.1 2,340 6/2/2023
Loading failed