-
Notifications
You must be signed in to change notification settings - Fork 561
Description
Problem
PATCH requests without a Content-Type header or with an empty body cause an AmbiguousMatchException
and return HTTP 500.
Error Details
Microsoft.AspNetCore.Routing.Matching.AmbiguousMatchException:
The request matched multiple endpoints. Matches:
FhirController.PatchJson (Microsoft.Health.Fhir.Api)
FhirController.PatchFhir (Microsoft.Health.Fhir.Api)
Root Cause
FhirController
has two PATCH methods with identical routes but different Consumes
attributes:
Method 1: PatchJson (~line 484)
[HttpPatch]
[Route(KnownRoutes.ResourceTypeById)]
[Consumes("application/json-patch+json")]
public async Task<IActionResult> PatchJson(
[FromBody] JsonPatchDocument<Resource> patch,
...)
Method 2: PatchFhir (~line 531)
[HttpPatch]
[Route(KnownRoutes.ResourceTypeById)]
[Consumes("application/fhir+json")]
public async Task<IActionResult> PatchFhir(
[FromBody] Parameters parameters,
...)
ASP.NET Core evaluates route matching before applying Consumes
constraints and before model binding. When a PATCH request arrives without a Content-Type
header or with an empty body, the router cannot distinguish between these methods.
Reproduction
PATCH https://{{hostname}}/Patient/example
(No Content-Type header, no body)
Result: HTTP 500 with AmbiguousMatchException
Impact
- Users receive HTTP 500 instead of proper validation error
- Occurs with testing tools (PostmanRuntime) or malformed requests
- Low frequency but poor user experience
Recommended Solution
Mark [FromBody]
parameters as required to fail early with a clear error instead of ambiguous match:
Option 1: Use EmptyBodyBehavior.Disallow
(Preferred)
public async Task<IActionResult> PatchJson(
[FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Disallow)] JsonPatchDocument<Resource> patch,
...)
public async Task<IActionResult> PatchFhir(
[FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Disallow)] Parameters parameters,
...)
This approach:
- ✅ Fails early during model binding (before routing ambiguity)
- ✅ Returns HTTP 400 automatically
- ✅ Minimal code change
- ✅ Leverages built-in ASP.NET Core functionality
Option 2: Custom Validation Middleware
Add middleware to reject PATCH requests with empty bodies before routing.
Option 3: Explicit Empty Body Handling
Handle the empty body case explicitly in both methods and return HTTP 400.
Testing Checklist
- PATCH without Content-Type → HTTP 400 Bad Request
- PATCH with empty body → HTTP 400 Bad Request
- Error message indicates Content-Type and body are required
- PATCH with
Content-Type: application/json-patch+json
+ valid body → routes to PatchJson - PATCH with
Content-Type: application/fhir+json
+ valid body → routes to PatchFhir - No AmbiguousMatchException in telemetry after deployment
- E2E tests cover all scenarios
- Integration tests verify routing and validation behavior
- Unit tests verify EmptyBodyBehavior.Disallow behavior