Rename across every client surface using each language's idiomatic convention:
* .NET clients/dotnet/MxGateway.Client[.Cli|.Tests]/
-> clients/dotnet/ZB.MOM.WW.MxGateway.Client[.Cli|.Tests]/
namespaces -> ZB.MOM.WW.MxGateway.Client[.Cli|.Tests]
contracts ProjectReference repointed to ZB.MOM.WW.MxGateway.Contracts
sln migrated to slnx (dotnet sln migrate)
* Python src/mxgateway -> src/zb_mom_ww_mxgateway
src/mxgateway_cli -> src/zb_mom_ww_mxgateway_cli
distribution: mxaccess-gateway-client -> zb-mom-ww-mxaccess-gateway-client
* Rust crate: mxgateway-client -> zb-mom-ww-mxgateway-client
build.rs proto path repointed
* Java subprojects: mxgateway-{client,cli} -> zb-mom-ww-mxgateway-{client,cli}
packages com.dohertylan.mxgateway -> com.zb.mom.ww.mxgateway
group com.dohertylan.mxgateway -> com.zb.mom.ww.mxgateway
rootProject mxaccessgw-java -> zb-mom-ww-mxaccessgw-java
* Go generate-proto.ps1 proto path repointed; module path and
package mxgateway kept (Go convention).
* proto-inputs.json: generatedOutputs.python updated to new package path.
* scripts/run-client-e2e-tests.ps1: Java CLI install path + gradle task
updated to zb-mom-ww-mxgateway-cli.
CLI binary names (mxgw, mxgw-py, mxgw-go, mxgateway-cli) and wire-level
identifiers (MXGATEWAY_* env vars, the mxgw_<id>_<secret> API key
prefix, protobuf package names like mxaccess_gateway.v1, all MXAccess
references) intentionally NOT renamed.
Fix pre-existing alarms-over-gateway breaks unblocked by the rename:
* mxaccess_gateway.proto: add missing public message QueryActiveAlarmsRequest
{session_id, client_correlation_id, alarm_filter_prefix} and missing
rpc QueryActiveAlarms(QueryActiveAlarmsRequest) returns
(stream ActiveAlarmSnapshot). All four typed clients referenced
these but they were absent from the proto.
* MxAccessGatewayService.QueryActiveAlarms: implement the new RPC on
the server, streaming from IGatewayAlarmService.CurrentAlarms with
optional alarm_filter_prefix filter.
* clients/dotnet/.../DiscoverHierarchyOptions.cs: add the hand-written
.NET POCO that wraps DiscoverHierarchyRequest (referenced by
GalaxyRepositoryClient.DiscoverHierarchyAsync but never authored).
* Drop retired session_id field references from
AcknowledgeAlarmRequest/AcknowledgeAlarmReply test fixtures across
.NET, Rust, Go, and Python clients.
* Rust integration test: add the missing stream_alarms impl on the
fake MxAccessGateway server (the trait gained the method, fake
didn't).
* Rust CLI test: bump expected gatewayProtocolVersion 2 -> 3.
Regenerated artifacts updated in this commit:
* src/ZB.MOM.WW.MxGateway.Contracts/Generated/{MxaccessGateway,MxaccessGatewayGrpc}.cs
* clients/python/src/zb_mom_ww_mxgateway/generated/*_pb2{,_grpc}.py
* clients/go/internal/generated/*.pb.go
(C# regenerated by Grpc.Tools on contracts build; Python and Go via
their generate-proto.ps1 scripts; Rust regenerates from .proto via
tonic-build at compile time so no checked-in artefact.)
Verification: 472 server tests, 275 worker tests (9 dev-rig skipped),
18 integration tests (live MxAccess + LDAP + Galaxy), 57 .NET client
tests, 32 Rust workspace tests, 39 Python tests, all Go packages, and
gradle build for Java all pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
7.5 KiB
.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 for handwritten code and the Protobuf Style Guide for generated contract inputs.
Projects
Recommended layout:
clients/dotnet/
ZB.MOM.WW.MxGateway.Client.slnx
ZB.MOM.WW.MxGateway.Client/
ZB.MOM.WW.MxGateway.Client.csproj
GatewayClient.cs
MxGatewaySession.cs
MxGatewayClientOptions.cs
Authentication/
Conversion/
Errors/
Generated/
ZB.MOM.WW.MxGateway.Client.Cli/
ZB.MOM.WW.MxGateway.Client.Cli.csproj
Program.cs
Commands/
ZB.MOM.WW.MxGateway.Client.Tests/
ZB.MOM.WW.MxGateway.Client.Tests.csproj
ZB.MOM.WW.MxGateway.Client.IntegrationTests/
ZB.MOM.WW.MxGateway.Client.IntegrationTests.csproj
Target framework:
<TargetFramework>net10.0</TargetFramework>
The scaffold uses a project reference to
src/ZB.MOM.WW.MxGateway.Contracts/ZB.MOM.WW.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.ClientGoogle.ProtobufGrpc.Toolsfor generationMicrosoft.Extensions.Logging.AbstractionsSystem.CommandLineor similar for CLI- test framework: xUnit or NUnit
Library API
Suggested public types:
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<IReadOnlyList<SubscribeResult>> AddItemBulkAsync(int serverHandle, IReadOnlyList<string> tagAddresses, CancellationToken ct = default);
public Task<IReadOnlyList<SubscribeResult>> AdviseItemBulkAsync(int serverHandle, IReadOnlyList<int> itemHandles, CancellationToken ct = default);
public Task<IReadOnlyList<SubscribeResult>> RemoveItemBulkAsync(int serverHandle, IReadOnlyList<int> itemHandles, CancellationToken ct = default);
public Task<IReadOnlyList<SubscribeResult>> UnAdviseItemBulkAsync(int serverHandle, IReadOnlyList<int> itemHandles, CancellationToken ct = default);
public Task<IReadOnlyList<SubscribeResult>> SubscribeBulkAsync(int serverHandle, IReadOnlyList<string> tagAddresses, CancellationToken ct = default);
public Task<IReadOnlyList<SubscribeResult>> UnsubscribeBulkAsync(int serverHandle, IReadOnlyList<int> itemHandles, 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
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 MxGatewayClientRetryOptions Retry { get; init; } = new();
public ILoggerFactory? LoggerFactory { get; init; }
}
The .NET client applies a bounded Polly retry policy only to idempotent calls:
CloseSession and diagnostic Invoke commands such as Ping,
GetSessionState, and GetWorkerInfo. It does not retry OpenSession, event
streams, writes, secured writes, authentication, registration, item management,
or subscription changes because those calls can partially succeed in MXAccess.
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:
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:
MxGatewayException
MxGatewayAuthenticationException
MxGatewayAuthorizationException
MxGatewaySessionException
MxGatewayWorkerException
MxGatewayCommandException
MxAccessException
For command replies that include MXAccess HRESULT/status, prefer returning the reply and exposing helper methods:
reply.EnsureProtocolSuccess();
reply.EnsureMxAccessSuccess();
Test CLI
Project: ZB.MOM.WW.MxGateway.Client.Cli.
Command examples:
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,
RegisterAsyncbuilds the right command payload,AddItem2Asyncincludes context,WriteAsyncconverts scalar and array values,- command reply status helpers preserve MXAccess HRESULT,
StreamEventsAsyncyields ordered events,- stream cancellation disposes the call,
- CLI parsing and JSON output.
Integration Tests
Use xUnit traits or categories. Skip unless:
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.