diff --git a/src/Hl7.Fhir.WebApi.AspNetCore/FhirMediaTypeFormatter.cs b/src/Hl7.Fhir.WebApi.AspNetCore/FhirMediaTypeFormatter.cs
index 22dd2a0c..ae656029 100644
--- a/src/Hl7.Fhir.WebApi.AspNetCore/FhirMediaTypeFormatter.cs
+++ b/src/Hl7.Fhir.WebApi.AspNetCore/FhirMediaTypeFormatter.cs
@@ -16,6 +16,8 @@
using System.Linq;
using Hl7.Fhir.Serialization;
using Hl7.Fhir.Utility;
+using Microsoft.Extensions.Primitives;
+using System.Resources;
namespace Hl7.Fhir.WebApi
{
@@ -120,6 +122,71 @@ protected override bool CanWriteType(Type type)
return false;
}
+ ///
+ public override bool CanWriteResult(OutputFormatterCanWriteContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ if (SupportedMediaTypes.Count == 0)
+ {
+ var message = $"No media types found in '{GetType().FullName}.{nameof(SupportedMediaTypes)}'. Add at least one media type to the list of supported media types.";
+
+ throw new InvalidOperationException(message);
+ }
+
+ if (!CanWriteType(context.ObjectType))
+ {
+ return false;
+ }
+
+ if (!context.ContentType.HasValue)
+ {
+ // If the desired content type is set to null, then the current formatter can write anything
+ // it wants.
+ context.ContentType = new StringSegment(SupportedMediaTypes[0]);
+ return true;
+ }
+ else
+ {
+ var parsedContentType = new MediaType(context.ContentType);
+ for (var i = 0; i < SupportedMediaTypes.Count; i++)
+ {
+ var supportedMediaType = new MediaType(SupportedMediaTypes[i]);
+ if (supportedMediaType.HasWildcard)
+ {
+ // For supported media types that are wildcard patterns, confirm that the requested
+ // media type satisfies the wildcard pattern (e.g., if "text/entity+json;v=2" requested
+ // and formatter supports "text/*+json").
+ // We only do this when comparing against server-defined content types (e.g., those
+ // from [Produces] or Response.ContentType), otherwise we'd potentially be reflecting
+ // back arbitrary Accept header values.
+ if (context.ContentTypeIsServerDefined
+ && parsedContentType.IsSubsetOf(supportedMediaType))
+ {
+ return true;
+ }
+ }
+ else
+ {
+ // For supported media types that are not wildcard patterns, confirm that this formatter
+ // supports a more specific media type than requested e.g. OK if "text/*" requested and
+ // formatter supports "text/plain".
+ // contentType is typically what we got in an Accept header.
+ if (supportedMediaType.IsSubsetOfIgnoreParameters(parsedContentType))
+ {
+ context.ContentType = new StringSegment(SupportedMediaTypes[i]);
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
const string x_correlation_id = "X-Correlation-Id";
public override void WriteResponseHeaders(OutputFormatterWriteContext context)
{
diff --git a/src/Hl7.Fhir.WebApi.AspNetCore/Utility.cs b/src/Hl7.Fhir.WebApi.AspNetCore/Utility.cs
index 57b6d561..735a5f17 100644
--- a/src/Hl7.Fhir.WebApi.AspNetCore/Utility.cs
+++ b/src/Hl7.Fhir.WebApi.AspNetCore/Utility.cs
@@ -17,6 +17,7 @@
using System.Net;
using System.Net.Http;
using System.Text;
+using Microsoft.AspNetCore.Mvc.Formatters;
namespace Hl7.Fhir.WebApi
{
@@ -185,5 +186,67 @@ public static string ToFhirDateTime(this System.DateTime? me)
}
#endregion
+ #region << Static Helpers for Header Subset comparison that ignores Header parameters >>
+ public static bool IsSubsetOfIgnoreParameters(this MediaType supported, MediaType set)
+ {
+ return supported.MatchesType(set) &&
+ supported.MatchesSubtype(set);
+ }
+
+ private static bool MatchesType(this MediaType supported, MediaType set)
+ {
+ return set.MatchesAllTypes ||
+ set.Type.Equals(supported.Type, StringComparison.OrdinalIgnoreCase);
+ }
+
+ private static bool MatchesSubtype(this MediaType supported, MediaType set)
+ {
+ if (set.MatchesAllSubTypes)
+ {
+ return true;
+ }
+
+ if (set.SubTypeSuffix.HasValue)
+ {
+ if (supported.SubTypeSuffix.HasValue)
+ {
+ // Both the set and the media type being checked have suffixes, so both parts must match.
+ return supported.MatchesSubtypeWithoutSuffix(set) && supported.MatchesSubtypeSuffix(set);
+ }
+ else
+ {
+ // The set has a suffix, but the media type being checked doesn't. We never consider this to match.
+ return false;
+ }
+ }
+ else
+ {
+ // If this subtype or suffix matches the subtype of the set,
+ // it is considered a subtype.
+ // Ex: application/json > application/val+json
+ return supported.MatchesEitherSubtypeOrSuffix(set);
+ }
+ }
+
+ private static bool MatchesSubtypeWithoutSuffix(this MediaType supported, MediaType set)
+ {
+ return set.MatchesAllSubTypesWithoutSuffix ||
+ set.SubTypeWithoutSuffix.Equals(supported.SubTypeWithoutSuffix, StringComparison.OrdinalIgnoreCase);
+ }
+
+ private static bool MatchesSubtypeSuffix(this MediaType supported, MediaType set)
+ {
+ // We don't have support for wildcards on suffixes alone (e.g., "application/entity+*")
+ // because there's no clear use case for it.
+ return set.SubTypeSuffix.Equals(supported.SubTypeSuffix, StringComparison.OrdinalIgnoreCase);
+ }
+
+ private static bool MatchesEitherSubtypeOrSuffix(this MediaType supported, MediaType set)
+ {
+ return set.SubType.Equals(supported.SubType, StringComparison.OrdinalIgnoreCase) ||
+ set.SubType.Equals(supported.SubTypeSuffix, StringComparison.OrdinalIgnoreCase);
+ }
+
+ #endregion
}
}
diff --git a/src/Test.WebApi.AspNetCore/BasicTests.cs b/src/Test.WebApi.AspNetCore/BasicTests.cs
index 7c22731d..fde79ca6 100644
--- a/src/Test.WebApi.AspNetCore/BasicTests.cs
+++ b/src/Test.WebApi.AspNetCore/BasicTests.cs
@@ -6,6 +6,7 @@
using Hl7.Fhir.Validation;
using Hl7.Fhir.WebApi;
using Microsoft.EntityFrameworkCore;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
@@ -14,6 +15,7 @@
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
+using System.Text.Json;
using System.Threading;
using Task = System.Threading.Tasks.Task;
@@ -951,6 +953,79 @@ public async System.Threading.Tasks.Task ReadBinary()
}
+ [TestMethod]
+ public async Task RequestAcceptJsonWithHeaderParameter()
+ {
+ var app = new UnitTestFhirServerApplication();
+ var httpClient = app.CreateClient();
+ var acceptHeader = "application/json+fhir";
+ var acceptHeaderWithEncoding = "application/json+fhir; charset=utf-8";
+ var acceptHeaderWithEncodingAndVersion = "application/json+fhir; carset=utf-8; fhirVersion=4.0";
+ var badAcceptHeader = "application/notjson+fhir";
+ var acceptXmlHeader = "application/xml+fhir";
+
+ httpClient.DefaultRequestHeaders.Add("Accept", acceptHeader);
+ var raw = await httpClient.GetStringAsync($"{app.Server.BaseAddress}Patient");
+
+ try
+ {
+ await new FhirJsonParser().ParseAsync(raw);
+ }
+ catch (Exception ex)
+ {
+ Assert.Fail("Expected Json formatted bundle: " + ex.Message);
+ }
+
+ httpClient.DefaultRequestHeaders.Remove("Accept");
+ httpClient.DefaultRequestHeaders.Add("Accept", acceptHeaderWithEncoding);
+ raw = await httpClient.GetStringAsync($"{app.Server.BaseAddress}Patient");
+ try
+ {
+ await new FhirJsonParser().ParseAsync(raw);
+ }
+ catch (Exception ex)
+ {
+ Assert.Fail("Expected Json formatted bundle: " + ex.Message);
+ }
+
+ httpClient.DefaultRequestHeaders.Remove("Accept");
+ httpClient.DefaultRequestHeaders.Add("Accept", acceptHeaderWithEncodingAndVersion);
+ raw = await httpClient.GetStringAsync($"{app.Server.BaseAddress}Patient");
+ try
+ {
+ await new FhirJsonParser().ParseAsync(raw);
+ }
+ catch (Exception ex)
+ {
+ Assert.Fail("Expected Json formatted bundle: " + ex.Message);
+ }
+
+ httpClient.DefaultRequestHeaders.Remove("Accept");
+ httpClient.DefaultRequestHeaders.Add("Accept", badAcceptHeader);
+ raw = await httpClient.GetStringAsync($"{app.Server.BaseAddress}Patient");
+ try
+ {
+ await new FhirXmlParser().ParseAsync(raw);
+ }
+ catch (Exception ex)
+ {
+ Assert.Fail("Expected Xml formatted bundle: " + ex.Message);
+ }
+
+ httpClient.DefaultRequestHeaders.Remove("Accept");
+ httpClient.DefaultRequestHeaders.Add("Accept", acceptXmlHeader);
+ raw = await httpClient.GetStringAsync($"{app.Server.BaseAddress}Patient");
+ try
+ {
+ await new FhirXmlParser().ParseAsync(raw);
+ }
+ catch (Exception ex)
+ {
+ Assert.Fail("Expected Xml formatted bundle: " + ex.Message);
+ }
+
+ }
+
private void ClientFhir_OnBeforeRequest(object sender, HttpRequestMessage msg)
{
System.Diagnostics.Trace.WriteLine("---------------------------------------------------");