Skip to content

Add client-side WebHttpBinding support (closes #1413, #46) - #5959

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

Add client-side WebHttpBinding support (closes #1413, #46)#5959
afifi-ins wants to merge 1 commit into
dotnet:mainfrom
afifi-ins:feature/webhttpbinding-porting

Conversation

@afifi-ins

@afifi-ins afifi-ins commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Closes #1413
Closes #46

Summary

Adds a new client-side System.ServiceModel.Web package for REST-style WCF clients on .NET, including:

  • WebHttpBinding, WebHttpSecurity, and HTTP/HTTPS transport settings
  • WebHttpBehavior, WebGetAttribute, and WebInvokeAttribute
  • WebChannelFactory<TChannel> and client-side WebOperationContext request/response contexts
  • UriTemplate, UriTemplateTable, matching/binding helpers, and QueryStringConverter
  • XML, JSON, and raw Stream message encoding, including buffered and streamed paths
  • XML/JSON reply formatter selection based on the response Content-Type

The implementation is sourced from the MIT-licensed .NET Framework reference source and CoreWCF, then adapted to current dotnet/wcf patterns and its client-only architecture.

Scope

This PR implements the WCF client surface. Service hosting and dispatch remain CoreWCF responsibilities, so server-side dispatch selectors/formatters, help pages, service error handlers, JSONP, ASP.NET hosting integration, and WebServiceHost are intentionally excluded.

Raw octet-stream support is included. The reusable ByteStreamMessage and ByteStreamMessageEncodingBindingElement implementation lives in System.ServiceModel.Primitives; its public surface is exposed only for the current .NET target, matching the implementation TFMs.

Compatibility and packaging

  • Adds the shipping System.ServiceModel.Web project, reference assembly, unit-test project, package README, and solution wiring.
  • Preserves compatibility with the runtime System.ServiceModel.Web facade by forwarding all 38 JSON and Syndication types exposed by that facade.
  • Keeps the Web package free of InternalsVisibleTo dependencies on System.ServiceModel.Http and System.ServiceModel.Primitives.
  • Documents deliberately retained .NET Framework UriTemplate and QueryStringConverter compatibility quirks.
  • Uses centrally managed package versions and includes synchronized localized resources.

Tests

  • 312 System.ServiceModel.Web unit tests covering bindings, security, URI templates, query conversion, attributes, operation context, behavior/formatters, XML/JSON/raw encoders, buffered/streamed paths, and facade compatibility.
  • 12 WebHttp integration tests: 8 local construction/loopback tests pass; 4 service-host scenarios are conditionally skipped when the external WCF test endpoint is unavailable.
  • Focused System.ServiceModel.Primitives ByteStream tests cover buffering, offsets, ownership, repeated disposal, sync/async encoding, quotas, and validation.
  • Full Release solution build completes with 0 warnings and 0 errors.

The self-hosted CoreWCF outerloop scenarios require elevated certificate installation locally and are expected to run in the CI/elevated test environment.

Source attribution

Ported files retain the .NET Foundation MIT header. Commit history records whether each implementation came from the .NET Framework reference source or CoreWCF and documents client-specific adaptations.

@afifi-ins
afifi-ins force-pushed the feature/webhttpbinding-porting branch from a5caed5 to 794f262 Compare June 14, 2026 06:29
afifi-ins pushed a commit to afifi-ins/wcf that referenced this pull request Jun 14, 2026
CI failure analysis on PR dotnet#5959:
- All 12 'dotnet-wcf-ci' (non-corewcf) legs fail with HTML-500 responses from
  the shared bridge wcfcoresrv23.westus3.cloudapp.azure.com - an infra outage
  that affects every outerloop test (Binding.Http, Binding.WS, Client.*,
  Contract.*, Encoding.*, Extensibility.*, Security.*), NOT this PR.
- 'dotnet-wcf-with-corewcf--ci' (which uses local self-hosted CoreWCF, no
  bridge) is much cleaner - only one workitem fails: Binding.WebHttp.IntegrationTests.
  4 of 7 tests fail with:
  System.InvalidOperationException: Manual addressing is enabled on this
  factory, so all messages sent must be pre-addressed.

Root cause:
- The CoreWCF source we lifted for WebHttpBehavior.cs (Phase 5) had an empty
  ApplyClientBehavior - CoreWCF is server-only and never implemented the
  client-side wiring. As a result, no UriTemplateClientFormatter ever ran on
  outgoing messages, so the per-operation URI was never bound. The channel
  factory then tried to send each request to the endpoint base address
  (http://localhost:8081/WebHttp.svc/) with ManualAddressing = true on the
  HttpTransportBindingElement - failing fast in ApplyManualAddressing.
- The stub CoreWCF UriTemplateClientFormatter also threw
  PlatformNotSupportedException on every call - same reason.

Fix: port the real client-side wiring from the .NET Framework MIT-licensed
Reference Source mirror in mono/mono. Specifically:

1. src/.../Dispatcher/UriTemplateClientFormatter.cs:
   Replace the CoreWCF stub (DeserializeReply / SerializeRequest throw
   PlatformNotSupportedException) with the real .NET FX implementation
   (~150 LOC): binds operation parameters into the UriTemplate, sets
   Message.Headers.To from the bound URI, and applies SuppressEntityBody +
   Method on HttpRequestMessageProperty.
   Server-side WebOperationContext branch dropped; the client-only port uses
   the HttpRequestMessageProperty path unconditionally because dotnet/wcf's
   WebOperationContext does not expose OutgoingRequest (only OutgoingResponse,
   which is server-perspective).

2. src/.../Description/WebHttpBehavior.cs:
   - ApplyClientBehavior body replaced with the real .NET FX implementation:
     for each operation in the contract, build the request + reply client
     formatters, wrap in CompositeClientFormatter, set ClientOperation.Formatter,
     and add WebFaultClientMessageInspector.
   - Added the supporting client-side helper methods:
       GetRequestClientFormatter (the big one - ~80 LOC of URI-template +
         body-style routing, mirrors .NET FX)
       GetReplyClientFormatter (~30 LOC)
       GetDefaultClientFormatter (~30 LOC - harvests the WCF default formatter
         from a throwaway ClientOperation via IOperationBehavior.ApplyClientBehavior)
       GetDefaultXmlAndJsonClientFormatter (~10 LOC)
       GetDefaultContentType (~10 LOC)
       AddClientErrorInspector (~5 LOC)

3. src/.../Dispatcher/SingleBodyParameterMessageFormatter.cs:
   - Add IClientMessageFormatter interface (was IDispatchMessageFormatter only).
   - Add SerializeRequest, DeserializeReply, SuppressRequestEntityBody.
   - Add static factories CreateClientFormatter, CreateXmlAndJsonClientFormatter
     (mirror existing CreateDispatchFormatter / CreateXmlAndJsonDispatchFormatter).
   - Make nested NullMessageFormatter implement IClientMessageFormatter as well.

4. src/.../Dispatcher/HttpStreamFormatter.cs:
   - Add IClientMessageFormatter interface.
   - Add SerializeRequest (mirror of SerializeReply) and DeserializeReply.

5. New small client-side helper classes (each ~30 LOC, ported from .NET FX):
   - Dispatcher/CompositeClientFormatter.cs - request+reply pair.
   - Dispatcher/ContentTypeSettingClientMessageFormatter.cs - stamps
     outgoing Content-Type via HttpRequestMessageProperty (the .NET FX
     WebOperationContext branch is dropped for the same client-port reason).
   - Dispatcher/WebFaultClientMessageInspector.cs - surfaces HTTP 500 as
     CommunicationException so callers don't see empty payloads silently.

DemultiplexingClientMessageFormatter is deliberately NOT ported: the .NET FX
implementation switches on the inbound Content-Type to pick the XML or JSON
client formatter, but our client-side path returns the XML formatter directly
because [WebGet]/[WebInvoke].ResponseFormat already determines the wire format
at description time. Both modes (XML and JSON) round-trip through the same
SingleBodyParameter* formatter chain - the format mapping on WebHttpBinding
routing selects the encoder per-message via WebBodyFormatMessageProperty.

Verification:
- Full repo build (build.cmd -restore -build -configuration Release): 0 warnings, 0 errors.
- 3 unit tests pass locally (102ms): WebHttpBinding_CanBeConstructed,
  WebHttpBinding_TransportMode_UsesHttps, WebChannelFactory_Endpoint_HasWebHttpBinding.
- 4 outerloop tests will execute end-to-end in CI now that the formatter chain
  is wired (still requires SelfHostedCoreWcfService running locally; CI's
  'dotnet-wcf-with-corewcf--ci' leg launches it automatically).

Also rebased onto upstream/main (commit 36673ab - Skip SctRenewalRegressionTests
on CoreWCF host); no conflicts.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mconnew
mconnew force-pushed the feature/webhttpbinding-porting branch from a04e6f3 to 98ec5dd Compare July 20, 2026 20:35
mconnew pushed a commit to afifi-ins/wcf that referenced this pull request Jul 20, 2026
CI failure analysis on PR dotnet#5959:
- All 12 'dotnet-wcf-ci' (non-corewcf) legs fail with HTML-500 responses from
  the shared bridge wcfcoresrv23.westus3.cloudapp.azure.com - an infra outage
  that affects every outerloop test (Binding.Http, Binding.WS, Client.*,
  Contract.*, Encoding.*, Extensibility.*, Security.*), NOT this PR.
- 'dotnet-wcf-with-corewcf--ci' (which uses local self-hosted CoreWCF, no
  bridge) is much cleaner - only one workitem fails: Binding.WebHttp.IntegrationTests.
  4 of 7 tests fail with:
  System.InvalidOperationException: Manual addressing is enabled on this
  factory, so all messages sent must be pre-addressed.

Root cause:
- The CoreWCF source we lifted for WebHttpBehavior.cs (Phase 5) had an empty
  ApplyClientBehavior - CoreWCF is server-only and never implemented the
  client-side wiring. As a result, no UriTemplateClientFormatter ever ran on
  outgoing messages, so the per-operation URI was never bound. The channel
  factory then tried to send each request to the endpoint base address
  (http://localhost:8081/WebHttp.svc/) with ManualAddressing = true on the
  HttpTransportBindingElement - failing fast in ApplyManualAddressing.
- The stub CoreWCF UriTemplateClientFormatter also threw
  PlatformNotSupportedException on every call - same reason.

Fix: port the real client-side wiring from the .NET Framework MIT-licensed
Reference Source mirror in mono/mono. Specifically:

1. src/.../Dispatcher/UriTemplateClientFormatter.cs:
   Replace the CoreWCF stub (DeserializeReply / SerializeRequest throw
   PlatformNotSupportedException) with the real .NET FX implementation
   (~150 LOC): binds operation parameters into the UriTemplate, sets
   Message.Headers.To from the bound URI, and applies SuppressEntityBody +
   Method on HttpRequestMessageProperty.
   Server-side WebOperationContext branch dropped; the client-only port uses
   the HttpRequestMessageProperty path unconditionally because dotnet/wcf's
   WebOperationContext does not expose OutgoingRequest (only OutgoingResponse,
   which is server-perspective).

2. src/.../Description/WebHttpBehavior.cs:
   - ApplyClientBehavior body replaced with the real .NET FX implementation:
     for each operation in the contract, build the request + reply client
     formatters, wrap in CompositeClientFormatter, set ClientOperation.Formatter,
     and add WebFaultClientMessageInspector.
   - Added the supporting client-side helper methods:
       GetRequestClientFormatter (the big one - ~80 LOC of URI-template +
         body-style routing, mirrors .NET FX)
       GetReplyClientFormatter (~30 LOC)
       GetDefaultClientFormatter (~30 LOC - harvests the WCF default formatter
         from a throwaway ClientOperation via IOperationBehavior.ApplyClientBehavior)
       GetDefaultXmlAndJsonClientFormatter (~10 LOC)
       GetDefaultContentType (~10 LOC)
       AddClientErrorInspector (~5 LOC)

3. src/.../Dispatcher/SingleBodyParameterMessageFormatter.cs:
   - Add IClientMessageFormatter interface (was IDispatchMessageFormatter only).
   - Add SerializeRequest, DeserializeReply, SuppressRequestEntityBody.
   - Add static factories CreateClientFormatter, CreateXmlAndJsonClientFormatter
     (mirror existing CreateDispatchFormatter / CreateXmlAndJsonDispatchFormatter).
   - Make nested NullMessageFormatter implement IClientMessageFormatter as well.

4. src/.../Dispatcher/HttpStreamFormatter.cs:
   - Add IClientMessageFormatter interface.
   - Add SerializeRequest (mirror of SerializeReply) and DeserializeReply.

5. New small client-side helper classes (each ~30 LOC, ported from .NET FX):
   - Dispatcher/CompositeClientFormatter.cs - request+reply pair.
   - Dispatcher/ContentTypeSettingClientMessageFormatter.cs - stamps
     outgoing Content-Type via HttpRequestMessageProperty (the .NET FX
     WebOperationContext branch is dropped for the same client-port reason).
   - Dispatcher/WebFaultClientMessageInspector.cs - surfaces HTTP 500 as
     CommunicationException so callers don't see empty payloads silently.

DemultiplexingClientMessageFormatter is deliberately NOT ported: the .NET FX
implementation switches on the inbound Content-Type to pick the XML or JSON
client formatter, but our client-side path returns the XML formatter directly
because [WebGet]/[WebInvoke].ResponseFormat already determines the wire format
at description time. Both modes (XML and JSON) round-trip through the same
SingleBodyParameter* formatter chain - the format mapping on WebHttpBinding
routing selects the encoder per-message via WebBodyFormatMessageProperty.

Verification:
- Full repo build (build.cmd -restore -build -configuration Release): 0 warnings, 0 errors.
- 3 unit tests pass locally (102ms): WebHttpBinding_CanBeConstructed,
  WebHttpBinding_TransportMode_UsesHttps, WebChannelFactory_Endpoint_HasWebHttpBinding.
- 4 outerloop tests will execute end-to-end in CI now that the formatter chain
  is wired (still requires SelfHostedCoreWcfService running locally; CI's
  'dotnet-wcf-with-corewcf--ci' leg launches it automatically).

Also rebased onto upstream/main (commit 36673ab - Skip SctRenewalRegressionTests
on CoreWCF host); no conflicts.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mconnew mconnew assigned Copilot and unassigned Copilot Jul 22, 2026
Comment thread src/System.ServiceModel.Web/src/System/ServiceModel/WebHttpBinding.cs Outdated
@mconnew

mconnew commented Jul 23, 2026

Copy link
Copy Markdown
Member

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 2 pipeline(s).

@mconnew

mconnew commented Jul 24, 2026

Copy link
Copy Markdown
Member

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 2 pipeline(s).

Comment thread release-notes/SupportedFeatures-v2.1.0.md
Comment thread src/System.ServiceModel.Http/src/System.ServiceModel.Http.csproj Outdated
@mconnew

mconnew commented Jul 24, 2026

Copy link
Copy Markdown
Member
{

Consider replacing with ObjectPool<T> from the runtime.


Refers to: src/System.ServiceModel.Web/src/System/ServiceModel/Pool.cs:9 in db4e189. [](commit_id = db4e189, deletion_comment = False)

afifi-ins added a commit to afifi-ins/wcf that referenced this pull request Jul 28, 2026
… message

The HttpClientCredentialTypeInvalid resource string used to list valid
client credential values as 'None, Basic, Client, Digest, Ntlm, Windows'.
There is no 'Client' member on HttpClientCredentialType; the intended
value is 'Certificate' (see src/System.ServiceModel.Http/src/System/
ServiceModel/HttpClientCredentialType.cs). Every caller of
SR.HttpClientCredentialTypeInvalid (WSHttpBinding, BasicHttpBinding,
BasicHttpsBinding, NetHttpBinding, NetHttpsBinding, and the new
WebHttpBinding guard added by this PR) was therefore telling users to
use a value that does not exist.

Fix the wording in all three shipping resx files:

  * System.ServiceModel.Http/src/Resources/Strings.resx
  * System.ServiceModel.Primitives/src/Resources/Strings.resx
  * System.ServiceModel.Web/src/Resources/Strings.resx

Arcade's XliffTasks regenerated the 39 companion xlf files during the
next build. The English <source> is now correct in every locale; the
localized <target> strings that inlined the value verbatim are flagged
state='needs-review-translation' so the localization team can refresh
them in a follow-up localization sync.

svcutil's SRServiceModel.resx keeps the pre-existing typo (out of scope
per task direction).

Flagged by Claude Sonnet 5 during multi-model review of PR dotnet#5959. No
code-behind changes; SR key and format argument count are unchanged so
all five call sites and the new WebHttp guard keep working. All 39 in-repo
WebHttp tests still pass (0 failed).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
afifi-ins added a commit to afifi-ins/wcf that referenced this pull request Jul 28, 2026
All three WebHttpBinding_*_RoundTripsAgainstLocalHttpListener tests
used to hardcode a port (18091 / 18092 / 18093) and silently 'return'
on HttpListenerException. If the hardcoded port was already in use on
the test runner, the test would report as PASSED without running any
assertion - masking a real regression in the client's URL binding,
JSON reply deserialization, or cookie handling. Flagged by Gemini 3.1
Pro during multi-model code review of PR dotnet#5959.

Add a StartLoopbackHttpListener helper that:
  * Picks a random port from the Windows dynamic / ephemeral range
    (49152-65535 per RFC 6335), minimizing collisions with configured
    services.
  * Retries up to MaxPortRetries (10) times to survive transient
    collisions or parallel-test races.
  * On exhaustion, calls Assert.Fail with a per-attempt diagnostic
    listing each attempted port and its HttpListener error code.
    Verified in a temp failure-mode run: 'Unable to find a random port
    number after 10 attempts. Errors: attempt 1 port 1: 5/Access is
    denied; ...'

Refactor all three call sites to use the helper: one-line tuple
deconstruction replaces the seven-line hardcoded-port + try/catch/return
boilerplate in each test. Downstream logic is unchanged.

Semantics change vs before: an HttpListener environment that truly
blocks loopback binding after 10 tries now fails the test loudly
instead of skipping silently. This is the intended trade-off - a real
environmental block is worth surfacing, and 10 attempts across 16384
ports guarantees any transient collision is handled.

Local validation: 39 passed / 0 failed / 4 skipped (outerloop). Full
repo build clean.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mconnew

mconnew commented Jul 28, 2026

Copy link
Copy Markdown
Member
public class UriTemplate

No need for the type forward as this won't be consumed by assemblies compiled for .NET 3.5


Refers to: src/System.ServiceModel.Web/src/System/UriTemplate.cs:19 in a0df75f. [](commit_id = a0df75f, deletion_comment = False)

@mconnew

mconnew commented Jul 28, 2026

Copy link
Copy Markdown
Member

Update to use naming conventions for instance fields


Refers to: src/System.ServiceModel.Web/src/System/UriTemplate.cs:22 in a0df75f. [](commit_id = a0df75f, deletion_comment = False)

@mconnew

mconnew commented Jul 28, 2026

Copy link
Copy Markdown
Member
        }

Use nameof for arguments/parameters that are referenced by exceptions.


Refers to: src/System.ServiceModel.Web/src/System/UriTemplate.cs:57 in a0df75f. [](commit_id = a0df75f, deletion_comment = False)

@mconnew

mconnew commented Jul 28, 2026

Copy link
Copy Markdown
Member
        : this(template, ignoreTrailingSlash, null)

Formatting, there should be a blank line between each method/constructor


Refers to: src/System.ServiceModel.Web/src/System/UriTemplate.cs:45 in a0df75f. [](commit_id = a0df75f, deletion_comment = False)

@mconnew

mconnew commented Jul 28, 2026

Copy link
Copy Markdown
Member
        this.ignoreTrailingSlash = ignoreTrailingSlash;

this. shouldn't generally be needed and the current coding standards says to omit it if not needed.


Refers to: src/System.ServiceModel.Web/src/System/UriTemplate.cs:59 in a0df75f. [](commit_id = a0df75f, deletion_comment = False)

@mconnew

mconnew commented Jul 28, 2026

Copy link
Copy Markdown
Member
                SR.UTBadBaseAddress));

SR.Format is only needed when there string resource is parameterized. When it isn't parameterized, you can use the string resource directly without calling SR.Format.


Refers to: src/System.ServiceModel.Web/src/System/UriTemplate.cs:337 in a0df75f. [](commit_id = a0df75f, deletion_comment = False)

@mconnew

mconnew commented Jul 28, 2026

Copy link
Copy Markdown
Member
    //  templates as such based on the structure of them and not based on the set of uri

Nit: Typo in comment. Intuative -> intuitive.


Refers to: src/System.ServiceModel.Web/src/System/UriTemplate.cs:410 in a0df75f. [](commit_id = a0df75f, deletion_comment = False)

@mconnew

mconnew commented Jul 28, 2026

Copy link
Copy Markdown
Member
            // and warning suppression isn't working

This can probably be removed as it was included to stop a false positive on a static analysis rule in the .NET Framework source code. Replace with an Fx.Assert that they are not null to ensure the stated assertion holds true.


Refers to: src/System.ServiceModel.Web/src/System/UriTemplate.cs:429 in a0df75f. [](commit_id = a0df75f, deletion_comment = False)

@mconnew

mconnew commented Jul 28, 2026

Copy link
Copy Markdown
Member
        {

Formatting, we put blank lines after closing braces of code block, unless the next line is another closing brace.


Refers to: src/System.ServiceModel.Web/src/System/UriTemplate.cs:451 in a0df75f. [](commit_id = a0df75f, deletion_comment = False)

@mconnew

mconnew commented Jul 28, 2026

Copy link
Copy Markdown
Member
        Fx.Assert(segmentsCount >= this.firstOptionalSegment - 1, "How can that be? The Trie is constructed that way!");

Nit: extra space on final line of comment


Refers to: src/System.ServiceModel.Web/src/System/UriTemplate.cs:582 in a0df75f. [](commit_id = a0df75f, deletion_comment = False)

@mconnew

mconnew commented Jul 28, 2026

Copy link
Copy Markdown
Member
    {

All methods should have their accessibility explicitly stated. We do rely on default accessibility.


Refers to: src/System.ServiceModel.Web/src/System/UriTemplate.cs:633 in a0df75f. [](commit_id = a0df75f, deletion_comment = False)

@mconnew

mconnew commented Jul 28, 2026

Copy link
Copy Markdown
Member
            }

Update to use expression bodies where appropriate.


Refers to: src/System.ServiceModel.Web/src/System/UriTemplate.cs:981 in a0df75f. [](commit_id = a0df75f, deletion_comment = False)

@mconnew

mconnew commented Jul 28, 2026

Copy link
Copy Markdown
Member
            }

Remove dead code. Have Copilot look for other commented out dead code and remove if appropriate


Refers to: src/System.ServiceModel.Web/src/System/UriTemplate.cs:562 in a0df75f. [](commit_id = a0df75f, deletion_comment = False)

@mconnew

mconnew commented Jul 28, 2026

Copy link
Copy Markdown
Member
using System.Runtime;

Move using's outside of namespace in all files.


Refers to: src/System.ServiceModel.Web/src/System/UriTemplateLiteralPathSegment.cs:7 in a0df75f. [](commit_id = a0df75f, deletion_comment = False)

@mconnew

mconnew commented Jul 28, 2026

Copy link
Copy Markdown
Member

Fix naming to match naming conventions for all private static fields. Also a reminder about explicit accessibility being specified.


Refers to: src/System.ServiceModel.Web/src/System/UriTemplateLiteralPathSegment.cs:19 in a0df75f. [](commit_id = a0df75f, deletion_comment = False)

@mconnew

mconnew commented Jul 28, 2026

Copy link
Copy Markdown
Member
{

I think there might be some async method overrides that need to be added to this class.


Refers to: src/System.ServiceModel.Web/src/System/ServiceModel/Channels/HttpStreamMessage.cs:11 in a0df75f. [](commit_id = a0df75f, deletion_comment = False)

@mconnew

mconnew commented Jul 28, 2026

Copy link
Copy Markdown
Member
    {

I couldn't find anywhere this was used


Refers to: src/System.ServiceModel.Web/src/System/ServiceModel/Channels/MessageExtensions.cs:11 in a0df75f. [](commit_id = a0df75f, deletion_comment = False)

@mconnew

mconnew commented Jul 28, 2026

Copy link
Copy Markdown
Member
                // Raw (Content-Type: application/octet-stream pass-through) support is deferred

ByteStreamMessageEncodingBindingElement should be ported into the Primitives package as it has utility outside of WebHttpBinding. Add it to this PR.


Refers to: src/System.ServiceModel.Web/src/System/ServiceModel/Channels/WebMessageEncoderFactory.cs:111 in a0df75f. [](commit_id = a0df75f, deletion_comment = False)

@mconnew

mconnew commented Jul 28, 2026

Copy link
Copy Markdown
Member
    {

It looks like this was ported over from the CoreWCF code base as it's referencing IServiceProvider. Have AI re-port this from the NetFx codebase as we don't need IServiceProvider for the client.


Refers to: src/System.ServiceModel.Web/src/System/ServiceModel/Description/WebHttpBehavior.cs:38 in a0df75f. [](commit_id = a0df75f, deletion_comment = False)

@mconnew

mconnew commented Jul 28, 2026

Copy link
Copy Markdown
Member
{

IDispatchMessageFormatter isn't needed for WebHttpBinding as it's only doing Request/Reply. IDispatchMessageFormatter is only used for duplex transports on the client.


Refers to: src/System.ServiceModel.Web/src/System/ServiceModel/Dispatcher/HttpStreamFormatter.cs:12 in a0df75f. [](commit_id = a0df75f, deletion_comment = False)

@mconnew

mconnew commented Jul 28, 2026

Copy link
Copy Markdown
Member
            throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(

Make sure this along with any other references is fixed once ByteStreamMessageEncodingBindingElement is ported.


Refers to: src/System.ServiceModel.Web/src/System/ServiceModel/Dispatcher/HttpStreamFormatter.cs:77 in a0df75f. [](commit_id = a0df75f, deletion_comment = False)

@mconnew

mconnew commented Jul 28, 2026

Copy link
Copy Markdown
Member

I think an ImmutableHashSet<Type> would be a better type to use for storing the default supported query string types.


Refers to: src/System.ServiceModel.Web/src/System/ServiceModel/Dispatcher/QueryStringConverter.cs:19 in a0df75f. [](commit_id = a0df75f, deletion_comment = False)

@mconnew

mconnew commented Jul 28, 2026

Copy link
Copy Markdown
Member
{

Drop the IDispatchMessageFormatter as not needed on the client side


Refers to: src/System.ServiceModel.Web/src/System/ServiceModel/Dispatcher/SingleBodyParameterMessageFormatter.cs:16 in a0df75f. [](commit_id = a0df75f, deletion_comment = False)

@mconnew

mconnew commented Jul 28, 2026

Copy link
Copy Markdown
Member
{

A performance issue was found with this code in CoreWCF and was replace with something else. I can't remember whether the replacement is usable here as I think it might have used an asp.net core class which the WCF Client can't reference. Take a look and see if it is something in the main runtime that I switched to. If not, then ask Copilot for an alternative. There might be something in the newer HttpClient headers api's which provides this parsing capability. Either way, we need to strip this code out and replace its usage with something else.


Refers to: src/System.ServiceModel.Web/src/System/ServiceModel/Web/HttpDateParse.cs:19 in a0df75f. [](commit_id = a0df75f, deletion_comment = False)

@mconnew

mconnew commented Jul 28, 2026

Copy link
Copy Markdown
Member
{

This is a service side class to represent an incoming request. On the client side we should only have IncomingWebResponseContext and OutgoingWebResponseContext


Refers to: src/System.ServiceModel.Web/src/System/ServiceModel/Web/IncomingWebRequestContext.cs:16 in a0df75f. [](commit_id = a0df75f, deletion_comment = False)

@mconnew

mconnew commented Jul 28, 2026

Copy link
Copy Markdown
Member
{

Same for this class, this is a service side object representing sending a response to the client. It shouldn't be in this codebase


Refers to: src/System.ServiceModel.Web/src/System/ServiceModel/Web/OutgoingWebResponseContext.cs:16 in a0df75f. [](commit_id = a0df75f, deletion_comment = False)

@mconnew

mconnew commented Jul 28, 2026

Copy link
Copy Markdown
Member
{

This interface is only used for server side code.


Refers to: src/System.ServiceModel.Web/src/System/ServiceModel/Web/IWebFaultException.cs:10 in a0df75f. [](commit_id = a0df75f, deletion_comment = False)

@mconnew

mconnew commented Jul 28, 2026

Copy link
Copy Markdown
Member
{

Once the unnecessary service side code has been removed, check this class to make sure everything is still needed.


Refers to: src/System.ServiceModel.Web/src/System/ServiceModel/Web/Utility.cs:13 in a0df75f. [](commit_id = a0df75f, deletion_comment = False)

@mconnew

mconnew commented Jul 28, 2026

Copy link
Copy Markdown
Member
{

As far as I can tell, only used in service side code


Refers to: src/System.ServiceModel.Web/src/System/ServiceModel/Web/WebFaultException.cs:15 in a0df75f. [](commit_id = a0df75f, deletion_comment = False)

@mconnew

mconnew commented Jul 28, 2026

Copy link
Copy Markdown
Member

There's no incoming request on the client side


Refers to: src/System.ServiceModel.Web/src/System/ServiceModel/Web/WebOperationContext.cs:57 in a0df75f. [](commit_id = a0df75f, deletion_comment = False)

@mconnew

mconnew commented Jul 28, 2026

Copy link
Copy Markdown
Member

There's no outgoing response on the client side


Refers to: src/System.ServiceModel.Web/src/System/ServiceModel/Web/WebOperationContext.cs:59 in a0df75f. [](commit_id = a0df75f, deletion_comment = False)

@mconnew

mconnew commented Jul 29, 2026

Copy link
Copy Markdown
Member
    public void Attach(OperationContext owner)

This is missing:

         public IncomingWebResponseContext IncomingResponse { get; }

        public OutgoingWebRequestContext OutgoingRequest { get; }

Refers to: src/System.ServiceModel.Web/src/System/ServiceModel/Web/WebOperationContext.cs:60 in a0df75f. [](commit_id = a0df75f, deletion_comment = False)

@mconnew

mconnew commented Jul 29, 2026

Copy link
Copy Markdown
Member
    {

I believe the CreateXXXXXXXResponse methods are all unnecessary as the client doesn't create responses.


Refers to: src/System.ServiceModel.Web/src/System/ServiceModel/Web/WebOperationContext.cs:69 in a0df75f. [](commit_id = a0df75f, deletion_comment = False)

@mconnew

mconnew commented Jul 29, 2026

Copy link
Copy Markdown
Member
    {

Likely unneeded was the CreateXmlResponse methods go away


Refers to: src/System.ServiceModel.Web/src/System/ServiceModel/Web/WebOperationContext.cs:223 in a0df75f. [](commit_id = a0df75f, deletion_comment = False)

@afifi-ins
afifi-ins force-pushed the feature/webhttpbinding-porting branch from f4b0821 to 829c3f4 Compare July 30, 2026 04:44
afifi-ins pushed a commit to afifi-ins/wcf that referenced this pull request Jul 30, 2026
CI failure analysis on PR dotnet#5959:
- All 12 'dotnet-wcf-ci' (non-corewcf) legs fail with HTML-500 responses from
  the shared bridge wcfcoresrv23.westus3.cloudapp.azure.com - an infra outage
  that affects every outerloop test (Binding.Http, Binding.WS, Client.*,
  Contract.*, Encoding.*, Extensibility.*, Security.*), NOT this PR.
- 'dotnet-wcf-with-corewcf--ci' (which uses local self-hosted CoreWCF, no
  bridge) is much cleaner - only one workitem fails: Binding.WebHttp.IntegrationTests.
  4 of 7 tests fail with:
  System.InvalidOperationException: Manual addressing is enabled on this
  factory, so all messages sent must be pre-addressed.

Root cause:
- The CoreWCF source we lifted for WebHttpBehavior.cs (Phase 5) had an empty
  ApplyClientBehavior - CoreWCF is server-only and never implemented the
  client-side wiring. As a result, no UriTemplateClientFormatter ever ran on
  outgoing messages, so the per-operation URI was never bound. The channel
  factory then tried to send each request to the endpoint base address
  (http://localhost:8081/WebHttp.svc/) with ManualAddressing = true on the
  HttpTransportBindingElement - failing fast in ApplyManualAddressing.
- The stub CoreWCF UriTemplateClientFormatter also threw
  PlatformNotSupportedException on every call - same reason.

Fix: port the real client-side wiring from the .NET Framework MIT-licensed
Reference Source mirror in mono/mono. Specifically:

1. src/.../Dispatcher/UriTemplateClientFormatter.cs:
   Replace the CoreWCF stub (DeserializeReply / SerializeRequest throw
   PlatformNotSupportedException) with the real .NET FX implementation
   (~150 LOC): binds operation parameters into the UriTemplate, sets
   Message.Headers.To from the bound URI, and applies SuppressEntityBody +
   Method on HttpRequestMessageProperty.
   Server-side WebOperationContext branch dropped; the client-only port uses
   the HttpRequestMessageProperty path unconditionally because dotnet/wcf's
   WebOperationContext does not expose OutgoingRequest (only OutgoingResponse,
   which is server-perspective).

2. src/.../Description/WebHttpBehavior.cs:
   - ApplyClientBehavior body replaced with the real .NET FX implementation:
     for each operation in the contract, build the request + reply client
     formatters, wrap in CompositeClientFormatter, set ClientOperation.Formatter,
     and add WebFaultClientMessageInspector.
   - Added the supporting client-side helper methods:
       GetRequestClientFormatter (the big one - ~80 LOC of URI-template +
         body-style routing, mirrors .NET FX)
       GetReplyClientFormatter (~30 LOC)
       GetDefaultClientFormatter (~30 LOC - harvests the WCF default formatter
         from a throwaway ClientOperation via IOperationBehavior.ApplyClientBehavior)
       GetDefaultXmlAndJsonClientFormatter (~10 LOC)
       GetDefaultContentType (~10 LOC)
       AddClientErrorInspector (~5 LOC)

3. src/.../Dispatcher/SingleBodyParameterMessageFormatter.cs:
   - Add IClientMessageFormatter interface (was IDispatchMessageFormatter only).
   - Add SerializeRequest, DeserializeReply, SuppressRequestEntityBody.
   - Add static factories CreateClientFormatter, CreateXmlAndJsonClientFormatter
     (mirror existing CreateDispatchFormatter / CreateXmlAndJsonDispatchFormatter).
   - Make nested NullMessageFormatter implement IClientMessageFormatter as well.

4. src/.../Dispatcher/HttpStreamFormatter.cs:
   - Add IClientMessageFormatter interface.
   - Add SerializeRequest (mirror of SerializeReply) and DeserializeReply.

5. New small client-side helper classes (each ~30 LOC, ported from .NET FX):
   - Dispatcher/CompositeClientFormatter.cs - request+reply pair.
   - Dispatcher/ContentTypeSettingClientMessageFormatter.cs - stamps
     outgoing Content-Type via HttpRequestMessageProperty (the .NET FX
     WebOperationContext branch is dropped for the same client-port reason).
   - Dispatcher/WebFaultClientMessageInspector.cs - surfaces HTTP 500 as
     CommunicationException so callers don't see empty payloads silently.

DemultiplexingClientMessageFormatter is deliberately NOT ported: the .NET FX
implementation switches on the inbound Content-Type to pick the XML or JSON
client formatter, but our client-side path returns the XML formatter directly
because [WebGet]/[WebInvoke].ResponseFormat already determines the wire format
at description time. Both modes (XML and JSON) round-trip through the same
SingleBodyParameter* formatter chain - the format mapping on WebHttpBinding
routing selects the encoder per-message via WebBodyFormatMessageProperty.

Verification:
- Full repo build (build.cmd -restore -build -configuration Release): 0 warnings, 0 errors.
- 3 unit tests pass locally (102ms): WebHttpBinding_CanBeConstructed,
  WebHttpBinding_TransportMode_UsesHttps, WebChannelFactory_Endpoint_HasWebHttpBinding.
- 4 outerloop tests will execute end-to-end in CI now that the formatter chain
  is wired (still requires SelfHostedCoreWcfService running locally; CI's
  'dotnet-wcf-with-corewcf--ci' leg launches it automatically).

Also rebased onto upstream/main (commit 36673ab - Skip SctRenewalRegressionTests
on CoreWCF host); no conflicts.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
afifi-ins added a commit to afifi-ins/wcf that referenced this pull request Jul 30, 2026
… message

The HttpClientCredentialTypeInvalid resource string used to list valid
client credential values as 'None, Basic, Client, Digest, Ntlm, Windows'.
There is no 'Client' member on HttpClientCredentialType; the intended
value is 'Certificate' (see src/System.ServiceModel.Http/src/System/
ServiceModel/HttpClientCredentialType.cs). Every caller of
SR.HttpClientCredentialTypeInvalid (WSHttpBinding, BasicHttpBinding,
BasicHttpsBinding, NetHttpBinding, NetHttpsBinding, and the new
WebHttpBinding guard added by this PR) was therefore telling users to
use a value that does not exist.

Fix the wording in all three shipping resx files:

  * System.ServiceModel.Http/src/Resources/Strings.resx
  * System.ServiceModel.Primitives/src/Resources/Strings.resx
  * System.ServiceModel.Web/src/Resources/Strings.resx

Arcade's XliffTasks regenerated the 39 companion xlf files during the
next build. The English <source> is now correct in every locale; the
localized <target> strings that inlined the value verbatim are flagged
state='needs-review-translation' so the localization team can refresh
them in a follow-up localization sync.

svcutil's SRServiceModel.resx keeps the pre-existing typo (out of scope
per task direction).

Flagged by Claude Sonnet 5 during multi-model review of PR dotnet#5959. No
code-behind changes; SR key and format argument count are unchanged so
all five call sites and the new WebHttp guard keep working. All 39 in-repo
WebHttp tests still pass (0 failed).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
afifi-ins added a commit to afifi-ins/wcf that referenced this pull request Jul 30, 2026
All three WebHttpBinding_*_RoundTripsAgainstLocalHttpListener tests
used to hardcode a port (18091 / 18092 / 18093) and silently 'return'
on HttpListenerException. If the hardcoded port was already in use on
the test runner, the test would report as PASSED without running any
assertion - masking a real regression in the client's URL binding,
JSON reply deserialization, or cookie handling. Flagged by Gemini 3.1
Pro during multi-model code review of PR dotnet#5959.

Add a StartLoopbackHttpListener helper that:
  * Picks a random port from the Windows dynamic / ephemeral range
    (49152-65535 per RFC 6335), minimizing collisions with configured
    services.
  * Retries up to MaxPortRetries (10) times to survive transient
    collisions or parallel-test races.
  * On exhaustion, calls Assert.Fail with a per-attempt diagnostic
    listing each attempted port and its HttpListener error code.
    Verified in a temp failure-mode run: 'Unable to find a random port
    number after 10 attempts. Errors: attempt 1 port 1: 5/Access is
    denied; ...'

Refactor all three call sites to use the helper: one-line tuple
deconstruction replaces the seven-line hardcoded-port + try/catch/return
boilerplate in each test. Downstream logic is unchanged.

Semantics change vs before: an HttpListener environment that truly
blocks loopback binding after 10 tries now fails the test loudly
instead of skipping silently. This is the intended trade-off - a real
environmental block is worth surfacing, and 10 attempts across 16384
ports guarantees any transient collision is handled.

Local validation: 39 passed / 0 failed / 4 skipped (outerloop). Full
repo build clean.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@afifi-ins

Copy link
Copy Markdown
Contributor Author

I investigated the System.ServiceModel.Web simple-name collision with the runtime facade and pushed a compatibility fix in 05abde92f.

Findings:

  • A normal PackageReference consumer builds cleanly on both net10.0 and net11.0; NuGet/MSBuild selects this package assembly, so I could not reproduce MSB3243 in the shipping-consumer path.
  • The runtime still contains System.ServiceModel.Web, Version=4.0.0.0, PublicKeyToken=31bf3856ad364e35, while this package produces version 10.0.0.0 with token b03f5f7f11d50a3a. The default AssemblyLoadContext permits only one assembly per simple name, so the app-local package shadows the framework facade.
  • I reproduced the resulting binary break with a legacy IL assembly whose signatures reference the v4 facade's DataContractJsonSerializer and SyndicationFeed types. Before the fix, reflecting the first signature threw TypeLoadException because our assembly had none of the facade's forwards.

The fix makes the new assembly a strict superset of the framework facade by preserving its exact 38 JSON/Syndication type forwards, adds the existing centrally-versioned System.ServiceModel.Syndication dependency, and locks the forward set with a unit test. The same legacy-binary probe now resolves JSON to System.Private.DataContractSerialization and feeds to System.ServiceModel.Syndication successfully.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 8849d097-7c27-4ccb-98a9-3fae1d876771
@afifi-ins
afifi-ins force-pushed the feature/webhttpbinding-porting branch from 94fc0e6 to 928e6d6 Compare August 13, 2026 17:33
@afifi-ins

Copy link
Copy Markdown
Contributor Author

All review feedback is resolved on squashed head 928e6d6ca.

  • 5072893121: replaced the custom pool with runtime ObjectPool<T>; removed Pool.cs.
  • 5110479822: removed unnecessary TypeForwardedFrom attributes.
  • 5110483372: normalized instance fields to _camelCase.
  • 5110487977: replaced argument-name literals with nameof.
  • 5110490637: added required member spacing.
  • 5110494794: removed unnecessary this. qualifiers.
  • 5110507395: use resources directly when no formatting is required.
  • 5110513268: corrected the comment typo.
  • 5110526036: removed the obsolete suppression and retained the invariant with Fx.Assert.
  • 5110533365: normalized blank lines after code blocks.
  • 5110538647: removed trailing comment whitespace.
  • 5110544808: made member accessibility explicit.
  • 5110550914: simplified the cited wrappers to get-only auto-properties.
  • 5110557901: grouped interface implementations with regions.
  • 5110566784: normalized comment spacing across Web source.
  • 5110584133: removed unused AssertCanonical.
  • 5110587858: removed commented-out dead code.
  • 5110599002: moved all using directives outside namespaces.
  • 5110606854: fixed static-field naming and accessibility.
  • 5110642285: removed HttpStreamMessage; raw streams now use the complete shared ByteStream implementation.
  • 5110724902: removed unused MessageExtensions.
  • 5110915507: ported ByteStream encoding to Primitives with reference API and tests.
  • 5110929128: re-ported WebHttpBehavior as client-only with no IServiceProvider dependency.
  • 5110938838: HttpStreamFormatter now implements only IClientMessageFormatter.
  • 5110943378: raw request/reply handling now uses the ByteStream encoder.
  • 5110966047: default query types now use a shared FrozenSet<Type>.
  • 5110969586: removed IDispatchMessageFormatter from the single-body formatter.
  • 5110990620: removed unused server-only HttpDateParse after its consumers were removed.
  • 5111010034: removed server-only IncomingWebRequestContext.
  • 5111016609: removed server-only OutgoingWebResponseContext.
  • 5111035074: removed server-only IWebFaultException.
  • 5111041710: trimmed Utility to its remaining used helper.
  • 5111054881: removed server-only WebFaultException.
  • 5111063192: removed client-inapplicable IncomingRequest.
  • 5111064806: removed client-inapplicable OutgoingResponse.
  • 5111077148: added and wired IncomingResponse and OutgoingRequest.
  • 5111088028: removed client-inapplicable response factories.
  • 5111093957: removed CreateXmlResponse and related response helpers.

The five inline threads also have concise final replies and are marked resolved. CI is green.

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.

Add APIs to support WebHttpBinding Add support for HTTP requests which use the GET verb

3 participants