// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. using System.Collections.Generic; using System.Net.Http.Headers; namespace System.Net.Http.Formatting { /// /// Class that selects a for an /// or . /// public class DefaultContentNegotiator : IContentNegotiator { /// /// Performs content negotiating by selecting the most appropriate out of the passed in /// for the given that can serialize an object of the given /// . /// /// The type to be serialized. /// The request. /// The set of objects from which to choose. /// The result of the negotiation containing the most appropriate instance, /// or null if there is no appropriate formatter. public virtual ContentNegotiationResult Negotiate(Type type, HttpRequestMessage request, IEnumerable formatters) { if (type == null) { throw new ArgumentNullException("type"); } if (request == null) { throw new ArgumentNullException("request"); } if (formatters == null) { throw new ArgumentNullException("formatters"); } MediaTypeHeaderValue mediaType; MediaTypeFormatter formatter = RunNegotiation(type, request, formatters, out mediaType); if (formatter != null) { formatter = formatter.GetPerRequestFormatterInstance(type, request, mediaType); return new ContentNegotiationResult(formatter, mediaType); } return null; } private static MediaTypeFormatter RunNegotiation(Type type, HttpRequestMessage request, IEnumerable formatters, out MediaTypeHeaderValue mediaType) { // Asking to serialize a response. This is the nominal code path. // We ask all formatters for their best kind of match, and then we // choose the best among those. MediaTypeFormatter formatterMatchOnType = null; ResponseMediaTypeMatch mediaTypeMatchOnType = null; MediaTypeFormatter formatterMatchOnAcceptHeader = null; ResponseMediaTypeMatch mediaTypeMatchOnAcceptHeader = null; MediaTypeFormatter formatterMatchWithMapping = null; ResponseMediaTypeMatch mediaTypeMatchWithMapping = null; MediaTypeFormatter formatterMatchOnRequestContentType = null; ResponseMediaTypeMatch mediaTypeMatchOnRequestContentType = null; foreach (MediaTypeFormatter formatter in formatters) { ResponseMediaTypeMatch match = formatter.SelectResponseMediaType(type, request); if (match == null) { // Null signifies no match continue; } ResponseFormatterSelectionResult matchResult = match.ResponseFormatterSelectionResult; switch (matchResult) { case ResponseFormatterSelectionResult.MatchOnCanWriteType: // First match by type trumps all other type matches if (formatterMatchOnType == null) { formatterMatchOnType = formatter; mediaTypeMatchOnType = match; } break; case ResponseFormatterSelectionResult.MatchOnResponseContentType: // Match on response content trumps all other choices mediaType = match.MediaTypeMatch.MediaType; return formatter; case ResponseFormatterSelectionResult.MatchOnRequestAcceptHeader: // Matches on accept headers must choose the highest quality match double thisQuality = match.MediaTypeMatch.Quality; if (formatterMatchOnAcceptHeader != null) { double bestQualitySeen = mediaTypeMatchOnAcceptHeader.MediaTypeMatch.Quality; if (thisQuality <= bestQualitySeen) { continue; } } formatterMatchOnAcceptHeader = formatter; mediaTypeMatchOnAcceptHeader = match; break; case ResponseFormatterSelectionResult.MatchOnRequestWithMediaTypeMapping: // Matches on accept headers using mappings must choose the highest quality match double thisMappingQuality = match.MediaTypeMatch.Quality; if (mediaTypeMatchWithMapping != null) { double bestMappingQualitySeen = mediaTypeMatchWithMapping.MediaTypeMatch.Quality; if (thisMappingQuality <= bestMappingQualitySeen) { continue; } } formatterMatchWithMapping = formatter; mediaTypeMatchWithMapping = match; break; case ResponseFormatterSelectionResult.MatchOnRequestContentType: // First match on request content type trumps other request content matches if (formatterMatchOnRequestContentType == null) { formatterMatchOnRequestContentType = formatter; mediaTypeMatchOnRequestContentType = match; } break; } } // If we received matches based on both supported media types and from media type mappings, // we want to give precedence to the media type mappings, but only if their quality is >= that of the supported media type. // We do this because media type mappings are the user's extensibility point and must take precedence over normal // supported media types in the case of a tie. The 99% case is where both have quality 1.0. if (mediaTypeMatchWithMapping != null && mediaTypeMatchOnAcceptHeader != null) { if (mediaTypeMatchOnAcceptHeader.MediaTypeMatch.Quality > mediaTypeMatchWithMapping.MediaTypeMatch.Quality) { formatterMatchWithMapping = null; } } // now select the formatter and media type // A MediaTypeMapping is highest precedence -- it is an extensibility point // allowing the user to override normal accept header matching if (formatterMatchWithMapping != null) { mediaType = mediaTypeMatchWithMapping.MediaTypeMatch.MediaType; return formatterMatchWithMapping; } else if (formatterMatchOnAcceptHeader != null) { mediaType = mediaTypeMatchOnAcceptHeader.MediaTypeMatch.MediaType; return formatterMatchOnAcceptHeader; } else if (formatterMatchOnRequestContentType != null) { mediaType = mediaTypeMatchOnRequestContentType.MediaTypeMatch.MediaType; return formatterMatchOnRequestContentType; } else if (formatterMatchOnType != null) { mediaType = mediaTypeMatchOnType.MediaTypeMatch.MediaType; return formatterMatchOnType; } mediaType = null; return null; } } }