refactor(galaxy): migrate to ZB.MOM.WW.MxGateway.* nupkg packages
v2-ci / build (push) Failing after 33s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped

Imports the freshly-rebuilt ZB.MOM.WW.MxGateway.Client + ZB.MOM.WW.MxGateway.Contracts
nupkgs (0.1.0) from /tmp/mxgw-dist. Replaces the vendored libs/ DLLs and the
pre-restructure MxGateway.* namespaces across the runtime Galaxy driver,
Galaxy.Browser, and their tests.

Key changes:
- nuget-packages/ added as a local feed via NuGet.config; .gitignore exempts it
  from the *.nupkg rule so the packages are tracked
- Directory.Packages.props pins both packages at 0.1.0
- 4 csprojs swap <Reference HintPath="libs/...dll"/> for <PackageReference/>
- 36 .cs files renamed `using MxGateway.*` -> `using ZB.MOM.WW.MxGateway.*`
- libs/ removed (vendored DLLs + README.md)

GalaxyBrowseSession rewritten around the new lazy API:
- RootAsync calls GalaxyRepositoryClient.BrowseAsync (returns LazyBrowseNodes)
  and caches them by TagName instead of bulk-fetching the whole hierarchy
- ExpandAsync looks up the cached LazyBrowseNode and calls its ExpandAsync,
  giving true one-wire-call-per-click instead of in-memory parent/child scan
- _byGobjectId + _hasChildrenSet dropped (LazyBrowseNode carries HasChildrenHint)
- AttributesAsync unchanged (already uses DiscoverHierarchyAsync MaxDepth=0)

Tests: Galaxy.Tests 245/245, Galaxy.Browser.Tests 10/10, AdminUI.Tests 66/66.
Pre-existing 12 solution errors unchanged (test sinks + Cli XML comments).
This commit is contained in:
Joseph Doherty
2026-05-29 07:14:18 -04:00
parent d1b6cff085
commit 560b327ee1
48 changed files with 105 additions and 285 deletions
+2
View File
@@ -21,6 +21,8 @@ desktop.ini
# NuGet
packages/
*.nupkg
# … but DO track repo-local feed for mxaccessgw client (not yet on public nuget.org).
!nuget-packages/*.nupkg
# Certificates
*.pfx
+2
View File
@@ -97,5 +97,7 @@
<PackageVersion Include="xunit" Version="2.9.2" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.0.2" />
<PackageVersion Include="xunit.v3" Version="1.1.0" />
<PackageVersion Include="ZB.MOM.WW.MxGateway.Client" Version="0.1.0" />
<PackageVersion Include="ZB.MOM.WW.MxGateway.Contracts" Version="0.1.0" />
</ItemGroup>
</Project>
+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
<add key="local-mxgw" value="./nuget-packages" />
</packageSources>
</configuration>
@@ -1,29 +1,26 @@
using System.Collections.Concurrent;
using MxGateway.Client;
using MxGateway.Contracts.Proto.Galaxy;
using ZB.MOM.WW.MxGateway.Client;
using ZB.MOM.WW.MxGateway.Contracts.Proto.Galaxy;
using ZB.MOM.WW.OtOpcUa.Commons.Browsing;
namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Browser;
/// <summary>
/// Galaxy browse over the gateway's <see cref="GalaxyRepositoryClient"/>.
/// The gateway returns the deployed hierarchy as a flat <see cref="GalaxyObject"/>
/// list, so this session fetches the full set once on <see cref="RootAsync"/>,
/// caches it by <c>TagName</c> (and <c>GobjectId</c> for parent lookup), and serves
/// subsequent <see cref="ExpandAsync"/> calls in-memory. Attribute fetches are
/// per-object via <c>DiscoverHierarchyAsync(MaxDepth=0, IncludeAttributes=true)</c>.
/// Owns the supplied <see cref="GalaxyRepositoryClient"/> and disposes it
/// best-effort. (Browse does not need an <c>MxGatewaySession</c> — that's only
/// required for live subscribe/write paths handled by the runtime driver.)
/// Lazy Galaxy browse over <see cref="GalaxyRepositoryClient.BrowseAsync"/>.
/// <see cref="RootAsync"/> returns the top-level <see cref="LazyBrowseNode"/>s
/// directly from the gateway; <see cref="ExpandAsync"/> fetches the direct children
/// of a previously-handed-out node via <see cref="LazyBrowseNode.ExpandAsync"/>
/// (one wire call per click, paginated internally by the client). Attribute fetches
/// are per-object via <c>DiscoverHierarchyAsync(MaxDepth=0, IncludeAttributes=true)</c>.
/// Owns the supplied <see cref="GalaxyRepositoryClient"/> and disposes it best-effort.
/// </summary>
internal sealed class GalaxyBrowseSession : IBrowseSession
{
private readonly GalaxyRepositoryClient _client;
private readonly ConcurrentDictionary<string, GalaxyObject> _byTagName = new(StringComparer.Ordinal);
private readonly ConcurrentDictionary<int, GalaxyObject> _byGobjectId = new();
private volatile bool _disposed;
private readonly ConcurrentDictionary<string, LazyBrowseNode> _byTagName = new(StringComparer.Ordinal);
private readonly SemaphoreSlim _rootGate = new(1, 1);
private HashSet<int> _hasChildrenSet = new();
private volatile bool _disposed;
private IReadOnlyList<LazyBrowseNode>? _roots;
/// <summary>Opaque token identifying this session in the AdminUI registry.</summary>
public Guid Token { get; } = Guid.NewGuid();
@@ -33,19 +30,20 @@ internal sealed class GalaxyBrowseSession : IBrowseSession
/// <summary>
/// Initializes a new session wrapping a connected repository client. The factory
/// in <c>GalaxyDriverBrowser</c> (Task 9) constructs the client via
/// in <c>GalaxyDriverBrowser</c> constructs the client via
/// <see cref="GalaxyRepositoryClient.Create"/> and hands it off here for the
/// session's lifetime.
/// </summary>
/// <param name="client">Galaxy repository client to query for hierarchy and attributes.</param>
/// <param name="client">Galaxy repository client to query for browse and attributes.</param>
internal GalaxyBrowseSession(GalaxyRepositoryClient client)
{
_client = client ?? throw new ArgumentNullException(nameof(client));
}
/// <summary>
/// Fetches the full Galaxy hierarchy from the gateway, populates the cache,
/// and returns the top-level objects (those with no parent in the deployed model).
/// Fetches the top-level <see cref="LazyBrowseNode"/>s from the gateway and
/// returns them as <see cref="BrowseNode"/>s. Result is cached; a second call
/// returns the cached roots without a re-fetch.
/// </summary>
public async Task<IReadOnlyList<BrowseNode>> RootAsync(CancellationToken cancellationToken)
{
@@ -53,29 +51,11 @@ internal sealed class GalaxyBrowseSession : IBrowseSession
await _rootGate.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
var all = await _client.DiscoverHierarchyAsync(
new DiscoverHierarchyOptions { IncludeAttributes = false }, cancellationToken)
_roots ??= await _client.BrowseAsync(new BrowseChildrenOptions(), cancellationToken)
.ConfigureAwait(false);
// Populate caches so later ExpandAsync calls can resolve children in-memory.
foreach (var obj in all)
{
_byTagName[obj.TagName] = obj;
_byGobjectId[obj.GobjectId] = obj;
}
// Precompute the set of GobjectIds that appear as a parent — used by
// Project to compute HasChildrenHint in O(1) instead of O(n²).
_hasChildrenSet = new HashSet<int>(_byGobjectId.Values.Select(o => o.ParentGobjectId));
// Roots are objects whose parent isn't part of the returned set (typically
// ParentGobjectId == 0 for WinPlatforms / top-level Areas).
var roots = all
.Where(o => o.ParentGobjectId == 0 || !_byGobjectId.ContainsKey(o.ParentGobjectId))
.ToList();
LastUsedUtc = DateTime.UtcNow;
return Project(roots);
return Project(_roots);
}
finally
{
@@ -84,27 +64,24 @@ internal sealed class GalaxyBrowseSession : IBrowseSession
}
/// <summary>
/// Returns the direct children of the cached Galaxy object identified by
/// <paramref name="nodeId"/> (the object's <c>TagName</c>). Throws
/// <see cref="ArgumentException"/> if the tag hasn't been handed out by a
/// prior Root/Expand call (i.e. it's not in the cache).
/// Fetches the direct children of the cached node identified by
/// <paramref name="nodeId"/> (the object's <c>TagName</c>) via
/// <see cref="LazyBrowseNode.ExpandAsync"/>. Throws <see cref="ArgumentException"/>
/// if the tag hasn't been handed out by a prior Root/Expand call.
/// </summary>
public Task<IReadOnlyList<BrowseNode>> ExpandAsync(string nodeId, CancellationToken cancellationToken)
public async Task<IReadOnlyList<BrowseNode>> ExpandAsync(string nodeId, CancellationToken cancellationToken)
{
ObjectDisposedException.ThrowIf(_disposed, this);
if (!_byTagName.TryGetValue(nodeId, out var parent))
if (!_byTagName.TryGetValue(nodeId, out var node))
{
throw new ArgumentException(
$"Galaxy object '{nodeId}' is not in the current browse-session cache. " +
"Re-open the browser or expand its parent first.", nameof(nodeId));
}
var children = _byGobjectId.Values
.Where(o => o.ParentGobjectId == parent.GobjectId)
.ToList();
await node.ExpandAsync(cancellationToken).ConfigureAwait(false);
LastUsedUtc = DateTime.UtcNow;
return Task.FromResult(Project(children));
return Project(node.Children);
}
/// <summary>
@@ -143,29 +120,25 @@ internal sealed class GalaxyBrowseSession : IBrowseSession
}
/// <summary>
/// Projects <see cref="GalaxyObject"/>s to <see cref="BrowseNode"/>s, ensuring
/// every projected object is also written to the by-tag cache so later
/// <see cref="ExpandAsync"/> calls can find it. Galaxy nodes are always
/// <see cref="BrowseNodeKind.Folder"/> — leaves only appear in the attribute
/// side-panel, never in the tree.
/// Projects <see cref="LazyBrowseNode"/>s to <see cref="BrowseNode"/>s, caching
/// each by <c>TagName</c> so a subsequent <see cref="ExpandAsync"/> can locate
/// it. Galaxy nodes are always <see cref="BrowseNodeKind.Folder"/> — leaves only
/// appear in the attribute side-panel.
/// </summary>
private IReadOnlyList<BrowseNode> Project(IReadOnlyList<GalaxyObject> nodes)
private IReadOnlyList<BrowseNode> Project(IReadOnlyList<LazyBrowseNode> nodes)
{
var result = new List<BrowseNode>(nodes.Count);
foreach (var obj in nodes)
foreach (var n in nodes)
{
// Belt-and-braces: ensure the cache holds every node we hand back so
// ExpandAsync can resolve it on the next round-trip.
_byTagName[obj.TagName] = obj;
_byGobjectId[obj.GobjectId] = obj;
var displayName = !string.IsNullOrEmpty(obj.ContainedName) ? obj.ContainedName : obj.TagName;
var hasChildrenHint = _hasChildrenSet.Contains(obj.GobjectId);
_byTagName[n.Object.TagName] = n;
var displayName = !string.IsNullOrEmpty(n.Object.ContainedName)
? n.Object.ContainedName
: n.Object.TagName;
result.Add(new BrowseNode(
NodeId: obj.TagName,
NodeId: n.Object.TagName,
DisplayName: displayName,
Kind: BrowseNodeKind.Folder,
HasChildrenHint: hasChildrenHint));
HasChildrenHint: n.HasChildrenHint));
}
return result;
}
@@ -2,7 +2,7 @@ using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using MxGateway.Client;
using ZB.MOM.WW.MxGateway.Client;
using ZB.MOM.WW.OtOpcUa.Commons.Browsing;
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Config;
@@ -13,23 +13,8 @@
<InternalsVisibleTo Include="ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Browser.Tests" />
</ItemGroup>
<ItemGroup>
<!-- Vendored mxaccessgw .NET client — same DLLs as Driver.Galaxy.
See Driver.Galaxy/libs/README.md for the unwinding plan. -->
<Reference Include="MxGateway.Client">
<HintPath>..\ZB.MOM.WW.OtOpcUa.Driver.Galaxy\libs\MxGateway.Client.dll</HintPath>
<Private>true</Private>
</Reference>
<Reference Include="MxGateway.Contracts">
<HintPath>..\ZB.MOM.WW.OtOpcUa.Driver.Galaxy\libs\MxGateway.Contracts.dll</HintPath>
<Private>true</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<!-- Transitive deps of the vendored MxGateway.Client.dll -->
<PackageReference Include="Google.Protobuf" />
<PackageReference Include="Grpc.Core.Api" />
<PackageReference Include="Grpc.Net.Client" />
<PackageReference Include="ZB.MOM.WW.MxGateway.Client" />
<PackageReference Include="ZB.MOM.WW.MxGateway.Contracts" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
<PackageReference Include="Polly.Core" />
</ItemGroup>
</Project>
@@ -1,6 +1,6 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using MxGateway.Contracts.Proto.Galaxy;
using ZB.MOM.WW.MxGateway.Contracts.Proto.Galaxy;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Browse;
@@ -1,4 +1,4 @@
using MxGateway.Contracts.Proto.Galaxy;
using ZB.MOM.WW.MxGateway.Contracts.Proto.Galaxy;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Browse;
@@ -1,5 +1,5 @@
using MxGateway.Client;
using MxGateway.Contracts.Proto.Galaxy;
using ZB.MOM.WW.MxGateway.Client;
using ZB.MOM.WW.MxGateway.Contracts.Proto.Galaxy;
namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Browse;
@@ -1,5 +1,5 @@
using MxGateway.Client;
using MxGateway.Contracts.Proto.Galaxy;
using ZB.MOM.WW.MxGateway.Client;
using ZB.MOM.WW.MxGateway.Contracts.Proto.Galaxy;
namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Browse;
@@ -1,4 +1,4 @@
using MxGateway.Contracts.Proto.Galaxy;
using ZB.MOM.WW.MxGateway.Contracts.Proto.Galaxy;
namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Browse;
@@ -1,4 +1,4 @@
using MxGateway.Contracts.Proto.Galaxy;
using ZB.MOM.WW.MxGateway.Contracts.Proto.Galaxy;
namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Browse;
@@ -1,4 +1,4 @@
using MxGateway.Contracts.Proto.Galaxy;
using ZB.MOM.WW.MxGateway.Contracts.Proto.Galaxy;
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Runtime;
namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Browse;
@@ -1,7 +1,7 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using MxGateway.Client;
using MxGateway.Contracts.Proto;
using ZB.MOM.WW.MxGateway.Client;
using ZB.MOM.WW.MxGateway.Contracts.Proto;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Browse;
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Config;
@@ -528,7 +528,7 @@ public sealed class GalaxyDriver
// If discovery hasn't run yet, build the client here so the watcher has a target.
// Driver.Galaxy-009 fix: guard with ??= so if BuildDefaultHierarchySource later runs
// it reuses this client rather than overwriting the field and leaking the first instance.
_ownedRepositoryClient ??= MxGateway.Client.GalaxyRepositoryClient.Create(
_ownedRepositoryClient ??= ZB.MOM.WW.MxGateway.Client.GalaxyRepositoryClient.Create(
BuildClientOptions(_options.Gateway));
var source = new GatewayGalaxyDeployWatchSource(_ownedRepositoryClient);
@@ -2,7 +2,7 @@ using System.Diagnostics.Metrics;
using System.Threading.Channels;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using MxGateway.Contracts.Proto;
using ZB.MOM.WW.MxGateway.Contracts.Proto;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Runtime;
@@ -1,6 +1,6 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using MxGateway.Client;
using ZB.MOM.WW.MxGateway.Client;
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Config;
namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Runtime;
@@ -1,6 +1,6 @@
using Microsoft.Extensions.Logging;
using MxGateway.Client;
using MxGateway.Contracts.Proto;
using ZB.MOM.WW.MxGateway.Client;
using ZB.MOM.WW.MxGateway.Contracts.Proto;
namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Runtime;
@@ -1,7 +1,7 @@
using System.Diagnostics.Metrics;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using MxGateway.Contracts.Proto;
using ZB.MOM.WW.MxGateway.Contracts.Proto;
namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Runtime;
@@ -1,8 +1,8 @@
using System.Collections.Concurrent;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using MxGateway.Client;
using MxGateway.Contracts.Proto;
using ZB.MOM.WW.MxGateway.Client;
using ZB.MOM.WW.MxGateway.Contracts.Proto;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Runtime;
@@ -1,5 +1,5 @@
using MxGateway.Client;
using MxGateway.Contracts.Proto;
using ZB.MOM.WW.MxGateway.Client;
using ZB.MOM.WW.MxGateway.Contracts.Proto;
// Use the generated nested status enum for the SetBufferedUpdateInterval reply check.
namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Runtime;
@@ -62,7 +62,7 @@ public sealed class GatewayGalaxySubscriber : IGalaxySubscriber
/// applied value and skip redundant calls.
/// </summary>
private async Task EnsureSessionIntervalAsync(
MxGateway.Client.MxGatewaySession session, int serverHandle, int intervalMs, CancellationToken cancellationToken)
ZB.MOM.WW.MxGateway.Client.MxGatewaySession session, int serverHandle, int intervalMs, CancellationToken cancellationToken)
{
lock (_intervalLock)
{
@@ -1,4 +1,4 @@
using MxGateway.Contracts.Proto;
using ZB.MOM.WW.MxGateway.Contracts.Proto;
namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Runtime;
@@ -1,5 +1,5 @@
using Google.Protobuf.WellKnownTypes;
using MxGateway.Contracts.Proto;
using ZB.MOM.WW.MxGateway.Contracts.Proto;
namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Runtime;
@@ -1,5 +1,5 @@
using Google.Protobuf.WellKnownTypes;
using MxGateway.Contracts.Proto;
using ZB.MOM.WW.MxGateway.Contracts.Proto;
namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Runtime;
@@ -1,6 +1,6 @@
using Microsoft.Extensions.Logging;
using MxGateway.Client;
using MxGateway.Contracts.Proto;
using ZB.MOM.WW.MxGateway.Client;
using ZB.MOM.WW.MxGateway.Contracts.Proto;
namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Runtime;
@@ -1,5 +1,5 @@
using System.Runtime.CompilerServices;
using MxGateway.Contracts.Proto;
using ZB.MOM.WW.MxGateway.Contracts.Proto;
namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Runtime;
@@ -19,34 +19,8 @@
</ItemGroup>
<ItemGroup>
<!-- Vendored mxaccessgw .NET client. Originally consumed via path-based
ProjectReference to the sibling repo, but the sibling repo restructured
and the MxGateway.Client.csproj path no longer exists. The DLLs in
libs/ are the last known-good build (May 2026); they reference proto
types from MxGateway.Contracts.dll using the pre-restructure namespace
(MxGateway.Contracts.Proto). See libs/README.md for the unwinding plan
once the sibling repo restores a client library or we migrate to the
new ZB.MOM.WW.MxGateway.Contracts.Proto namespace. -->
<Reference Include="MxGateway.Client">
<HintPath>libs\MxGateway.Client.dll</HintPath>
<Private>true</Private>
</Reference>
<Reference Include="MxGateway.Contracts">
<HintPath>libs\MxGateway.Contracts.dll</HintPath>
<Private>true</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<!-- Transitive deps the vendored MxGateway.Client.dll was actually built
against (verified by reflecting GetReferencedAssemblies on the DLL —
see libs/README.md). Versions align with the sibling mxaccessgw repo's
current Server / Worker projects so binary-compat stays close to what
the team uses elsewhere. Pre-Driver.Galaxy-016 the csproj declared
`Polly` (the v7 API) instead of `Polly.Core` (the v8 API the DLL was
built against) — a package-name mistake, not just a version skew —
which would surface as a runtime MissingMethodException the first
time the client's retry pipeline ran. -->
<PackageReference Include="ZB.MOM.WW.MxGateway.Client" />
<PackageReference Include="ZB.MOM.WW.MxGateway.Contracts" />
<PackageReference Include="Google.Protobuf" />
<PackageReference Include="Grpc.Core.Api" />
<PackageReference Include="Grpc.Net.Client" />
@@ -1,101 +0,0 @@
# Vendored MxGateway client DLLs
This directory holds binary copies of `MxGateway.Client.dll` and
`MxGateway.Contracts.dll` from the sibling `mxaccessgw` repo's last known-good
build (May 2026). The DLLs are referenced from the driver's csproj as
`<Reference HintPath="…" />` items rather than `ProjectReference`.
## Provenance
Both DLLs are built from this team's own `mxaccessgw` source tree — they are
not third-party binaries. The build commit + checksums below are recorded so
future readers can verify the artefacts match the expected source without
needing to ask the original author.
| File | Source commit | SHA-256 |
|---|---|---|
| `MxGateway.Client.dll` | `dd7ca1634e2d2b8a866c81f0009bf87ee9427750` (mxaccessgw repo, pre-restructure) | `3507f770adc8c1b27b2fc4645079c6e4e02d5c65b9545c12d637cd2a080a00bd` |
| `MxGateway.Contracts.dll` | `dd7ca1634e2d2b8a866c81f0009bf87ee9427750` (mxaccessgw repo, pre-restructure) | `437dc6cb6994c7c4d858c82f69af890732c7ffbfa0463fbd8a63ce7930d251b4` |
The build commit is the same for both DLLs and is embedded as
`AssemblyInformationalVersion` inside each binary — re-verify by running:
`ilspycmd <dll> | grep AssemblyInformationalVersion`.
To re-verify the checksums (e.g. after a clone):
```bash
sha256sum libs/MxGateway.Client.dll libs/MxGateway.Contracts.dll
```
If either SHA-256 or the embedded source commit no longer matches what's
listed above, the artefact has been replaced — verify before trusting.
## Why vendored
The sibling `mxaccessgw` repo restructured: the `clients/dotnet/MxGateway.Client`
project the driver previously referenced via path-based `ProjectReference` no
longer exists, and the proto contracts moved from the `MxGateway.Contracts.Proto`
namespace to `ZB.MOM.WW.MxGateway.Contracts.Proto`. The driver's source still
expects the pre-restructure namespace, so re-pointing at the new contracts would
require a global namespace rename across ~19 driver files PLUS reimplementing
the `MxGatewayClient` / `MxGatewaySession` / `GalaxyRepositoryClient` types the
old client library provided (the sibling repo dropped the client library
entirely, keeping only the contracts).
Vendoring the binaries unblocked the build in minutes instead of hours, freezes
the gateway contract surface at a known-good version, and preserves the option
to migrate properly later without an emergency rewrite.
## What's vendored
| File | Built against |
|---|---|
| `MxGateway.Client.dll` | net10.0, references `MxGateway.Contracts.dll` |
| `MxGateway.Contracts.dll` | net10.0, proto namespace `MxGateway.Contracts.Proto[.Galaxy]` |
The NuGet packages the vendored DLLs reference (verified by reflecting
`Assembly.GetReferencedAssemblies()` against `MxGateway.Client.dll`) are
declared as direct `PackageReference` in the driver csproj — when the dropped
`ProjectReference` was in place those packages were transitively provided;
with binary references the consumer must declare them explicitly:
| Package | Reason |
|---|---|
| `Google.Protobuf` 3.34.1 | Proto message types in `MxGateway.Contracts.dll` |
| `Grpc.Core.Api` 2.76.0 | Base gRPC client types in `MxGateway.Client.dll` |
| `Grpc.Net.Client` 2.76.0 | HTTP/2 transport used by `MxGatewayClient` |
| `Microsoft.Extensions.Logging.Abstractions` 10.0.7 | `ILogger` used by the client |
| `Polly.Core` 8.6.6 | Retry pipeline used by `MxGatewayClient` |
Versions match the sibling mxaccessgw repo's current Server / Worker
projects (`ZB.MOM.WW.MxGateway.Server.csproj`,
`ZB.MOM.WW.MxGateway.Worker.csproj`) so the runtime versions stay close to
what the gateway team uses. The pre-Driver.Galaxy-016 declarations were
incorrect — most visibly `Polly 8.5.2` was declared where the DLL actually
needs `Polly.Core` (a different package: `Polly` v7 is the older fluent API;
`Polly.Core` v8 is the modern resilience-pipeline API the gateway client was
built against). A `Polly` reference would have failed at runtime with
`MissingMethodException` the first time a retry pipeline ran.
## Decompiled-source archive
The vendored DLLs are byte-for-byte the build output. The full source can be
recovered with `ilspycmd MxGateway.Client.dll > MxGateway.Client.cs` if a code
review or audit needs it.
## How to unwind
Either path closes the vendored-binary debt:
1. **Sibling repo restores `MxGateway.Client.csproj`** (or publishes a NuGet
package). Switch the csproj back to a `ProjectReference` / `PackageReference`,
delete this directory.
2. **Driver migrates to the new `ZB.MOM.WW.MxGateway.Contracts.Proto`
namespace.** Global namespace rename across the ~19 consuming source files,
plus re-implementing `MxGatewayClient` / `MxGatewaySession` /
`GalaxyRepositoryClient` (≈2,200 LoC of behavioural client code) either
inlined into this driver or as a fresh sibling library. Delete this
directory.
Either way: when unwinding, also drop the five `PackageReference` lines added
to the csproj alongside the `<Reference>` items — the new ProjectReference /
PackageReference will provide them transitively again.
@@ -1,4 +1,4 @@
using MxGateway.Client;
using ZB.MOM.WW.MxGateway.Client;
using Shouldly;
using Xunit;
@@ -13,8 +13,8 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Browser.Tests;
/// <see cref="GalaxyRepositoryClient"/>, which only ships an <c>internal</c>
/// transport seam (<c>IGalaxyRepositoryClientTransport</c>) and an <c>internal</c>
/// constructor — both keyed via <c>InternalsVisibleTo</c> on the vendored
/// <c>MxGateway.Client</c> assembly, and only granted to that repo's own
/// <c>MxGateway.Client.Tests</c>. We can't substitute a fake transport from
/// <c>ZB.MOM.WW.MxGateway.Client</c> assembly, and only granted to that repo's own
/// <c>ZB.MOM.WW.MxGateway.Client.Tests</c>. We can't substitute a fake transport from
/// here without changing the upstream repo, and the public <c>Create</c>
/// factory always opens a real gRPC channel. So in-memory traversal coverage
/// (RootAsync / ExpandAsync / AttributesAsync, including the SecurityClass
@@ -26,21 +26,8 @@
</ItemGroup>
<ItemGroup>
<!-- Vendored mxaccessgw client + contracts DLLs. The Browser project under test
holds the same binary references; the explicit duplicates here let us
construct a GalaxyRepositoryClient against an unreachable endpoint for
dispose-idempotency coverage, and make MxGateway types visible to the
test assembly. See
..\..\..\src\Drivers\ZB.MOM.WW.OtOpcUa.Driver.Galaxy\libs\README.md for
the unwinding plan. -->
<Reference Include="MxGateway.Client">
<HintPath>..\..\..\src\Drivers\ZB.MOM.WW.OtOpcUa.Driver.Galaxy\libs\MxGateway.Client.dll</HintPath>
<Private>true</Private>
</Reference>
<Reference Include="MxGateway.Contracts">
<HintPath>..\..\..\src\Drivers\ZB.MOM.WW.OtOpcUa.Driver.Galaxy\libs\MxGateway.Contracts.dll</HintPath>
<Private>true</Private>
</Reference>
<PackageReference Include="ZB.MOM.WW.MxGateway.Client" />
<PackageReference Include="ZB.MOM.WW.MxGateway.Contracts" />
</ItemGroup>
</Project>
@@ -1,7 +1,7 @@
using System.Runtime.CompilerServices;
using System.Threading.Channels;
using Google.Protobuf.WellKnownTypes;
using MxGateway.Contracts.Proto.Galaxy;
using ZB.MOM.WW.MxGateway.Contracts.Proto.Galaxy;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
@@ -1,4 +1,4 @@
using MxGateway.Contracts.Proto.Galaxy;
using ZB.MOM.WW.MxGateway.Contracts.Proto.Galaxy;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
@@ -1,4 +1,4 @@
using MxGateway.Contracts.Proto;
using ZB.MOM.WW.MxGateway.Contracts.Proto;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
@@ -1,6 +1,6 @@
using System.Threading.Channels;
using Google.Protobuf.WellKnownTypes;
using MxGateway.Contracts.Proto;
using ZB.MOM.WW.MxGateway.Contracts.Proto;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
@@ -1,4 +1,4 @@
using MxGateway.Contracts.Proto;
using ZB.MOM.WW.MxGateway.Contracts.Proto;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
@@ -1,7 +1,7 @@
using System.Diagnostics.Metrics;
using System.Threading.Channels;
using Google.Protobuf.WellKnownTypes;
using MxGateway.Contracts.Proto;
using ZB.MOM.WW.MxGateway.Contracts.Proto;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
@@ -1,6 +1,6 @@
using System.Threading.Channels;
using Google.Protobuf.WellKnownTypes;
using MxGateway.Contracts.Proto;
using ZB.MOM.WW.MxGateway.Contracts.Proto;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
@@ -1,6 +1,6 @@
using System.Threading.Channels;
using Google.Protobuf.WellKnownTypes;
using MxGateway.Contracts.Proto;
using ZB.MOM.WW.MxGateway.Contracts.Proto;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
@@ -1,4 +1,4 @@
using MxGateway.Contracts.Proto.Galaxy;
using ZB.MOM.WW.MxGateway.Contracts.Proto.Galaxy;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
@@ -1,5 +1,5 @@
using System.Diagnostics;
using MxGateway.Contracts.Proto;
using ZB.MOM.WW.MxGateway.Contracts.Proto;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
@@ -183,9 +183,9 @@ public sealed class GalaxyTelemetryTests
private sealed class FakeHierarchy : IGalaxyHierarchySource
{
/// <inheritdoc />
public Task<IReadOnlyList<MxGateway.Contracts.Proto.Galaxy.GalaxyObject>> GetHierarchyAsync(
public Task<IReadOnlyList<ZB.MOM.WW.MxGateway.Contracts.Proto.Galaxy.GalaxyObject>> GetHierarchyAsync(
CancellationToken cancellationToken)
=> Task.FromResult<IReadOnlyList<MxGateway.Contracts.Proto.Galaxy.GalaxyObject>>(
=> Task.FromResult<IReadOnlyList<ZB.MOM.WW.MxGateway.Contracts.Proto.Galaxy.GalaxyObject>>(
[new(), new()]);
}
}
@@ -1,6 +1,6 @@
using System.Runtime.CompilerServices;
using Google.Protobuf.WellKnownTypes;
using MxGateway.Contracts.Proto;
using ZB.MOM.WW.MxGateway.Contracts.Proto;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
@@ -1,6 +1,6 @@
using Google.Protobuf;
using Google.Protobuf.WellKnownTypes;
using MxGateway.Contracts.Proto;
using ZB.MOM.WW.MxGateway.Contracts.Proto;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Runtime;
@@ -1,5 +1,5 @@
using Google.Protobuf.WellKnownTypes;
using MxGateway.Contracts.Proto;
using ZB.MOM.WW.MxGateway.Contracts.Proto;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Runtime;
@@ -1,4 +1,4 @@
using MxGateway.Contracts.Proto;
using ZB.MOM.WW.MxGateway.Contracts.Proto;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Runtime;
@@ -24,16 +24,7 @@
</ItemGroup>
<ItemGroup>
<!-- Vendored mxaccessgw contracts DLL. The driver under test holds the same
binary reference; this explicit duplicate lets tests construct
GalaxyObject / GalaxyAttribute / MxCommand / MxEvent fixtures directly
rather than only via the driver's public surface. See
..\..\..\src\Drivers\ZB.MOM.WW.OtOpcUa.Driver.Galaxy\libs\README.md for
the unwinding plan. -->
<Reference Include="MxGateway.Contracts">
<HintPath>..\..\..\src\Drivers\ZB.MOM.WW.OtOpcUa.Driver.Galaxy\libs\MxGateway.Contracts.dll</HintPath>
<Private>true</Private>
</Reference>
<PackageReference Include="ZB.MOM.WW.MxGateway.Contracts" />
</ItemGroup>
</Project>