Skip to content

Add System.ServiceModel.Msmq — client-side MSMQ transport#5958

Open
afifi-ins wants to merge 1 commit into
dotnet:mainfrom
afifi-ins:feature/msmq-porting
Open

Add System.ServiceModel.Msmq — client-side MSMQ transport#5958
afifi-ins wants to merge 1 commit into
dotnet:mainfrom
afifi-ins:feature/msmq-porting

Conversation

@afifi-ins

Copy link
Copy Markdown
Contributor

Adds a new client transport package, System.ServiceModel.Msmq, that
provides the WCF client-side surface of .NET Framework's NetMsmqBinding
and MsmqIntegrationBinding on modern .NET. Sending is wired end-to-end
via the MSMQ.Messaging NuGet (community port of System.Messaging);
listeners and any other server-side concern remain out of scope and
should live in CoreWCF.

Stats

  • 15 commits · 71 files · +5,536 LOC
  • 23 public types ported (full netfx client surface)
  • 150/150 unit tests pass
  • 3/5 scenario tests pass against real localhost MSMQ
    (2 transactional scenarios env-var-gated by design — see below)
  • 0 warnings, 0 errors under -warnaserror

Public surface (23 types)

  • System.ServiceModel: NetMsmqBinding, MsmqBindingBase,
    NetMsmqSecurity, NetMsmqSecurityMode, MsmqTransportSecurity,
    MessageSecurityOverMsmq, MsmqAuthenticationMode,
    MsmqEncryptionAlgorithm, MsmqSecureHashAlgorithm,
    DeadLetterQueue, QueueTransferProtocol, MsmqException,
    PoisonMessageException, MsmqPoisonMessageException
  • System.ServiceModel.Channels: MsmqBindingElementBase,
    MsmqTransportBindingElement
  • System.ServiceModel.MsmqIntegration: MsmqIntegrationBinding,
    MsmqIntegrationBindingElement, MsmqIntegrationSecurity,
    MsmqIntegrationSecurityMode, MsmqMessageSerializationFormat,
    MsmqMessage<T>, MsmqIntegrationMessageProperty

Key architectural decisions (open for review)

  1. MSMQ.Messaging 1.0.4 as a hard PackageReference instead of
    re-implementing the native MSMQ P/Invoke layer. Saves ~1,300 LOC
    from the reference source and matches netfx semantics one-for-one.
  2. net10.0 only (not net462). Full .NET Framework already ships
    the same public types in System.ServiceModel.dll; co-targeting
    produces CS0436 ambiguity for every public type.
  3. No ref/ project, no System.ServiceModel.Shim type-forwards
    — matches the modern pattern of NetNamedPipe, Federation,
    UnixDomainSocket.
  4. AddressAccessDeniedException mappings → CommunicationException
    because the type lives in System.ServiceModel.NetNamedPipe (not
    in Primitives) and shipping our own copy would conflict at
    consume time. Could be cleaned up by promoting it to Primitives.
  5. MessageQueueTransactionType.Automatic for ambient transactions —
    delegates to mqrt.dll's native DTC, no custom
    IEnlistmentNotification needed.
  6. Receive-side / server hosting out of scope — use CoreWCF.

Bugs caught + fixed during the port

  1. MessageQueueException.ErrorCode is the generic HRESULT
    (0x80004005), not the native MQ_ERROR_* code. Had to convert through
    MessageQueueErrorCode for MsmqException.Normalized to map.
    Regression: MsmqMessagingInteropTest.
  2. .NET 8+ disables implicit DTC promotion by default and the property
    cannot be flipped reliably from inside the xunit-console host. The
    two transactional scenarios are env-var-gated
    (WCF_MSMQ_ENABLE_DTC_TESTS=true); product correctness is verified
    out-of-process. Regression: MsmqConditionsTest.

CI

  • azure-pipelines-arcade-PR.yml needs no edits — Helix already
    discovers Scenarios/**/*.IntegrationTests.csproj.
  • eng/SendToHelix.proj gets a one-PropertyGroup HelixPreCommands
    to best-effort Enable-WindowsOptionalFeature MSMQ-Server +
    Start-Service MSMQ on Windows workers.
  • On non-admin workers the enable silently no-ops and
    [Condition(MsmqInstalled)] skips the scenarios.

Follow-ups (intentionally deferred)

  1. Session-gram framing for IOutputSessionChannel so the channel
    inter-ops with netfx WCF services hosted with SessionMode.Required.
    Today we send one MSMQ message per Send() with a uuid:{Guid}
    session id.
  2. MsmqUri.ActiveDirectory + DLQ translators — not on the
    happy path; non-breaking add.
  3. CoreWCF MSMQ host once CoreWCF ships an MSMQ transport package
    — replace the scenario tests' "send + read-back-via-MSMQ.Messaging"
    pattern with a real WCF client → CoreWCF host → WCF client
    round-trip.

Open questions for reviewers

  1. Approve MSMQ.Messaging as a runtime dependency, or prefer a
    P/Invoke reimplementation?
  2. Promote AddressAccessDeniedException from NetNamedPipe to
    Primitives so we can use the right mapping?
  3. Is session-gram framing a v1 blocker, or v1.1?
  4. Add Enable-WindowsOptionalFeature MSMQ-Server to
    windows.11.amd64.client.open Helix image directly, or keep the
    per-workitem enable in SendToHelix.proj?

afifi-ins added a commit to afifi-ins/wcf that referenced this pull request Jun 8, 2026
The Helix Linux and macOS legs were failing on three theory cases:
  MsmqConditionsTest.ImplicitDtcEnabled_HonorsEnvVar("true"|"TRUE"|"True")

Symptom:
  Assert.True() Failure
  Expected: True
  Actual:   False

Root cause: ConditionalTestDetectors.IsImplicitDtcEnabled() is
Windows-only by design (MSMQ itself is Windows-only) and returns
false on non-Windows hosts regardless of the env var. The
[SupportedOSPlatform("windows")] attribute on the test class is an
analyzer hint only — xunit still ran the tests on Linux / macOS Helix
queues and the Assert.True expectation no longer held.

Fix: assert the actual contract — true on Windows, false elsewhere —
so the test passes on every platform the dispatch runs on.

Verified locally on Windows: 150/150 unit tests pass.
Port the WCF MSMQ client transport (NetMsmqBinding and
MsmqIntegrationBinding) from .NET Framework into a new, shippable
System.ServiceModel.Msmq NuGet package. Enables modern .NET clients to
send SOAP and raw-body messages to MSMQ queues, with optional DTC
transaction enlistment. The package is Windows-only and self-contained on
the managed side (depends only on System.ServiceModel.Primitives).

Features
- NetMsmqBinding: SOAP-over-MSMQ with the full binding-element graph
  (MsmqTransportBindingElement, MsmqBindingElementBase, MsmqBindingBase).
- MsmqIntegrationBinding: interop with legacy MSMQ applications via
  MsmqMessage<T> and MsmqIntegrationMessageProperty (raw body, no SOAP
  envelope).
- Channel shapes: IOutputChannel and IOutputSessionChannel with their
  channel factories; MsmqUri address translators (net.msmq://, FormatName,
  private/direct).
- Native interop: a hand-rolled P/Invoke layer over mqrt.dll
  (UnsafeNativeMethods, SafeMsmqQueueHandle, NativeMsmqMessage, MsmqQueue)
  with no third-party runtime dependency.
- Transactions: MessageQueueTransactionType None/Single/Automatic;
  Automatic enlists the ambient System.Transactions transaction into
  MSMQ's native DTC via DtcTransactionBridge (no custom
  IEnlistmentNotification required).
- Error handling: MsmqException maps native MQ_ERROR_* HRESULTs to the
  corresponding WCF exceptions.
- MSMQ-Integration enums (AcknowledgeTypes, Acknowledgment, MessageType,
  MessagePriority) shipped by this package with values pinned to the
  native MQMSG_* constants.
- Security value types: NetMsmqSecurity, MsmqTransportSecurity,
  MessageSecurityOverMsmq, MsmqAuthenticationMode, MsmqEncryptionAlgorithm,
  MsmqSecureHashAlgorithm.

Shared-assembly change
- Promote AddressAccessDeniedException from System.ServiceModel.NetNamedPipe
  to System.ServiceModel.Primitives so the MSMQ transport can map
  MQ_ERROR_ACCESS_DENIED / MQ_ERROR_SHARING_VIOLATION to it without a
  cross-transport coupling. NetNamedPipe keeps a [TypeForwardedTo] for
  binary compatibility.

Testing
- 213 unit tests (binding shape, URI translation, exception mapping,
  native message layout, enum values, transaction-mode dispatch).
- 5 scenario tests against a real localhost MSMQ queue manager
  (Binding.Msmq.IntegrationTests); 2 are environment-gated for DTC.
- Helix CI wired to best-effort enable the Windows MSMQ feature; scenarios
  skip cleanly via [Condition(MsmqInstalled)] when MSMQ is unavailable.
- Localized string resources (13 languages); package validation passes
  with an empty CompatibilitySuppressions.xml.

Scope: client send path only. Service hosting / receive pipeline is out of
scope (see CoreWCF). Session-gram framing for IOutputSessionChannel is
deferred.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cffba073-486d-49e6-8683-cf30c1eb5e28
@afifi-ins
afifi-ins force-pushed the feature/msmq-porting branch from 5a373f1 to 64371d9 Compare July 24, 2026 04:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant