Files
mxaccessgw/docs/clients-dotnet-csharp-design.md
2026-04-26 19:25:07 -04:00

201 lines
5.8 KiB
Markdown

# .NET 10 C# Client Detailed Design
## Purpose
Provide an idiomatic .NET 10 C# client library for MXAccess Gateway, plus a test
CLI and unit tests. This client is for modern .NET callers and must not load
MXAccess COM.
Follow the [C# Style Guide](./style-guides/CSharpStyleGuide.md) for
handwritten code and the [Protobuf Style Guide](./style-guides/ProtobufStyleGuide.md)
for generated contract inputs.
## Projects
Recommended layout:
```text
clients/dotnet/
MxGateway.Client.sln
MxGateway.Client/
MxGateway.Client.csproj
GatewayClient.cs
MxGatewaySession.cs
MxGatewayClientOptions.cs
Authentication/
Conversion/
Errors/
Generated/
MxGateway.Client.Cli/
MxGateway.Client.Cli.csproj
Program.cs
Commands/
MxGateway.Client.Tests/
MxGateway.Client.Tests.csproj
MxGateway.Client.IntegrationTests/
MxGateway.Client.IntegrationTests.csproj
```
Target framework:
```xml
<TargetFramework>net10.0</TargetFramework>
```
The scaffold uses a project reference to
`src/MxGateway.Contracts/MxGateway.Contracts.csproj` for generated protobuf and
gRPC types. `clients/dotnet/generated` remains reserved for client-local
generator output if the .NET client later needs to decouple from the contracts
project.
Expected packages:
- `Grpc.Net.Client`
- `Google.Protobuf`
- `Grpc.Tools` for generation
- `Microsoft.Extensions.Logging.Abstractions`
- `System.CommandLine` or similar for CLI
- test framework: xUnit or NUnit
## Library API
Suggested public types:
```csharp
public sealed class MxGatewayClient : IAsyncDisposable
{
public static MxGatewayClient Create(MxGatewayClientOptions options);
public Task<MxGatewaySession> OpenSessionAsync(
OpenSessionOptions? options = null,
CancellationToken cancellationToken = default);
public Task<MxCommandReply> InvokeAsync(
MxCommandRequest request,
CancellationToken cancellationToken = default);
}
public sealed class MxGatewaySession : IAsyncDisposable
{
public string SessionId { get; }
public Task<int> RegisterAsync(string clientName, CancellationToken ct = default);
public Task UnregisterAsync(int serverHandle, CancellationToken ct = default);
public Task<int> AddItemAsync(int serverHandle, string item, CancellationToken ct = default);
public Task<int> AddItem2Async(int serverHandle, string item, string context, CancellationToken ct = default);
public Task AdviseAsync(int serverHandle, int itemHandle, CancellationToken ct = default);
public Task UnAdviseAsync(int serverHandle, int itemHandle, CancellationToken ct = default);
public Task WriteAsync(int serverHandle, int itemHandle, MxValue value, int userId, CancellationToken ct = default);
public IAsyncEnumerable<MxEvent> StreamEventsAsync(CancellationToken ct = default);
public Task CloseAsync(CancellationToken ct = default);
}
```
Generated protobuf types should remain available under a generated namespace.
Handwritten wrappers should not hide raw replies.
## Options
```csharp
public sealed class MxGatewayClientOptions
{
public required Uri Endpoint { get; init; }
public required string ApiKey { get; init; }
public bool UseTls { get; init; }
public string? CaCertificatePath { get; init; }
public string? ServerNameOverride { get; init; }
public TimeSpan ConnectTimeout { get; init; } = TimeSpan.FromSeconds(10);
public TimeSpan DefaultCallTimeout { get; init; } = TimeSpan.FromSeconds(30);
public ILoggerFactory? LoggerFactory { get; init; }
}
```
API key may be loaded from `MXGATEWAY_API_KEY` by the CLI, not implicitly by the
library constructor unless a helper explicitly says it does that.
## Auth Interceptor
Use a gRPC call credentials/interceptor layer to attach:
```text
authorization: Bearer <api key>
```
The interceptor must redact the key in logs and exceptions.
## Streaming
Expose `StreamEventsAsync` as `IAsyncEnumerable<MxEvent>`. On cancellation,
cancel the gRPC stream and surface `OperationCanceledException` only when the
caller initiated cancellation.
Do not reorder events.
## Error Handling
Recommended exceptions:
```csharp
MxGatewayException
MxGatewayAuthenticationException
MxGatewayAuthorizationException
MxGatewaySessionException
MxGatewayWorkerException
MxGatewayCommandException
MxAccessException
```
For command replies that include MXAccess HRESULT/status, prefer returning the
reply and exposing helper methods:
```csharp
reply.EnsureProtocolSuccess();
reply.EnsureMxAccessSuccess();
```
## Test CLI
Project: `MxGateway.Client.Cli`.
Command examples:
```powershell
mxgw-dotnet version
mxgw-dotnet smoke --endpoint http://localhost:5000 --api-key-env MXGATEWAY_API_KEY --item TestChildObject.TestInt
mxgw-dotnet stream-events --session-id <id> --json
mxgw-dotnet write --session-id <id> --server-handle 1 --item-handle 1 --type int32 --value 123
```
The CLI should use `System.CommandLine` or a similarly testable parser. JSON
output should be deterministic and redact API keys.
## Unit Tests
Use an in-process fake gRPC service with `Grpc.AspNetCore.Server` test host or
mock the generated client behind an internal interface.
Required tests:
- auth metadata is attached,
- API key is redacted,
- options build plaintext and TLS channels correctly,
- `RegisterAsync` builds the right command payload,
- `AddItem2Async` includes context,
- `WriteAsync` converts scalar and array values,
- command reply status helpers preserve MXAccess HRESULT,
- `StreamEventsAsync` yields ordered events,
- stream cancellation disposes the call,
- CLI parsing and JSON output.
## Integration Tests
Use xUnit traits or categories. Skip unless:
```text
MXGATEWAY_INTEGRATION=1
MXGATEWAY_ENDPOINT=<endpoint>
MXGATEWAY_API_KEY=<key>
MXGATEWAY_TEST_ITEM=<item>
```
Integration smoke should open, register, add, advise, stream for bounded time,
and close.