Use latest `Microsoft.AspNet.WebApi.Client` package
- #5822 - update `Microsoft.AspNet.WebApi.Client` package version - remove remaining few `$(PackageTargetFallback)` settings - remove .NET Standard-specific files - they duplicate what Microsoft.AspNet.WebApi.Client now provides - necessary only if cross-compiling a PCL version of the WebApiCompatShim - add type forwarding for the removed `public` types - reenable .NET Framework WebApiCompatShim tests - add breaking change record for `MediaTypeFormatterMatchRanking` value change - version of this type in the shim lacked the `MatchOnRequestWithMediaTypeMapping` value
This commit is contained in:
parent
9fd60ff864
commit
bab520f4dd
|
|
@ -40,7 +40,7 @@
|
||||||
<MicrosoftAspNetCoreTestHostPackageVersion>2.1.0-preview1-27965</MicrosoftAspNetCoreTestHostPackageVersion>
|
<MicrosoftAspNetCoreTestHostPackageVersion>2.1.0-preview1-27965</MicrosoftAspNetCoreTestHostPackageVersion>
|
||||||
<MicrosoftAspNetCoreTestingPackageVersion>2.1.0-preview1-27965</MicrosoftAspNetCoreTestingPackageVersion>
|
<MicrosoftAspNetCoreTestingPackageVersion>2.1.0-preview1-27965</MicrosoftAspNetCoreTestingPackageVersion>
|
||||||
<MicrosoftAspNetCoreWebUtilitiesPackageVersion>2.1.0-preview1-27965</MicrosoftAspNetCoreWebUtilitiesPackageVersion>
|
<MicrosoftAspNetCoreWebUtilitiesPackageVersion>2.1.0-preview1-27965</MicrosoftAspNetCoreWebUtilitiesPackageVersion>
|
||||||
<MicrosoftAspNetWebApiClientPackageVersion>5.2.2</MicrosoftAspNetWebApiClientPackageVersion>
|
<MicrosoftAspNetWebApiClientPackageVersion>5.2.4-preview1</MicrosoftAspNetWebApiClientPackageVersion>
|
||||||
<MicrosoftCodeAnalysisCSharpPackageVersion>2.3.1</MicrosoftCodeAnalysisCSharpPackageVersion>
|
<MicrosoftCodeAnalysisCSharpPackageVersion>2.3.1</MicrosoftCodeAnalysisCSharpPackageVersion>
|
||||||
<MicrosoftCodeAnalysisRazorPackageVersion>2.1.0-preview1-27965</MicrosoftCodeAnalysisRazorPackageVersion>
|
<MicrosoftCodeAnalysisRazorPackageVersion>2.1.0-preview1-27965</MicrosoftCodeAnalysisRazorPackageVersion>
|
||||||
<MicrosoftExtensionsCachingMemoryPackageVersion>2.1.0-preview1-27965</MicrosoftExtensionsCachingMemoryPackageVersion>
|
<MicrosoftExtensionsCachingMemoryPackageVersion>2.1.0-preview1-27965</MicrosoftExtensionsCachingMemoryPackageVersion>
|
||||||
|
|
|
||||||
|
|
@ -1,293 +0,0 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace System.Collections.Generic
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Helper extension methods for fast use of collections.
|
|
||||||
/// </summary>
|
|
||||||
internal static class CollectionExtensions
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Return a new array with the value added to the end. Slow and best suited to long lived arrays with few
|
|
||||||
/// writes relative to reads.
|
|
||||||
/// </summary>
|
|
||||||
public static T[] AppendAndReallocate<T>(this T[] array, T value)
|
|
||||||
{
|
|
||||||
Debug.Assert(array != null);
|
|
||||||
|
|
||||||
var originalLength = array.Length;
|
|
||||||
var newArray = new T[originalLength + 1];
|
|
||||||
array.CopyTo(newArray, 0);
|
|
||||||
newArray[originalLength] = value;
|
|
||||||
return newArray;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Return the enumerable as an Array, copying if required. Optimized for common case where it is an Array.
|
|
||||||
/// Avoid mutating the return value.
|
|
||||||
/// </summary>
|
|
||||||
public static T[] AsArray<T>(this IEnumerable<T> values)
|
|
||||||
{
|
|
||||||
Debug.Assert(values != null);
|
|
||||||
|
|
||||||
var array = values as T[];
|
|
||||||
if (array == null)
|
|
||||||
{
|
|
||||||
array = values.ToArray();
|
|
||||||
}
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Return the enumerable as a Collection of T, copying if required. Optimized for the common case where it is
|
|
||||||
/// a Collection of T and avoiding a copy if it implements IList of T. Avoid mutating the return value.
|
|
||||||
/// </summary>
|
|
||||||
public static Collection<T> AsCollection<T>(this IEnumerable<T> enumerable)
|
|
||||||
{
|
|
||||||
Debug.Assert(enumerable != null);
|
|
||||||
|
|
||||||
var collection = enumerable as Collection<T>;
|
|
||||||
if (collection != null)
|
|
||||||
{
|
|
||||||
return collection;
|
|
||||||
}
|
|
||||||
// Check for IList so that collection can wrap it instead of copying
|
|
||||||
var list = enumerable as IList<T>;
|
|
||||||
if (list == null)
|
|
||||||
{
|
|
||||||
list = new List<T>(enumerable);
|
|
||||||
}
|
|
||||||
return new Collection<T>(list);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Return the enumerable as a IList of T, copying if required. Avoid mutating the return value.
|
|
||||||
/// </summary>
|
|
||||||
public static IList<T> AsIList<T>(this IEnumerable<T> enumerable)
|
|
||||||
{
|
|
||||||
Debug.Assert(enumerable != null);
|
|
||||||
|
|
||||||
var list = enumerable as IList<T>;
|
|
||||||
if (list != null)
|
|
||||||
{
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
return new List<T>(enumerable);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Return the enumerable as a List of T, copying if required. Optimized for common case where it is an List of
|
|
||||||
/// T or a ListWrapperCollection of T. Avoid mutating the return value.
|
|
||||||
/// </summary>
|
|
||||||
public static List<T> AsList<T>(this IEnumerable<T> enumerable)
|
|
||||||
{
|
|
||||||
Debug.Assert(enumerable != null);
|
|
||||||
|
|
||||||
List<T> list = enumerable as List<T>;
|
|
||||||
if (list != null)
|
|
||||||
{
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
ListWrapperCollection<T> listWrapper = enumerable as ListWrapperCollection<T>;
|
|
||||||
if (listWrapper != null)
|
|
||||||
{
|
|
||||||
return listWrapper.ItemsList;
|
|
||||||
}
|
|
||||||
return new List<T>(enumerable);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Remove values from the list starting at the index start.
|
|
||||||
/// </summary>
|
|
||||||
public static void RemoveFrom<T>(this List<T> list, int start)
|
|
||||||
{
|
|
||||||
Debug.Assert(list != null);
|
|
||||||
Debug.Assert(start >= 0 && start <= list.Count);
|
|
||||||
|
|
||||||
list.RemoveRange(start, list.Count - start);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Return the only value from list, the type's default value if empty, or call the errorAction for 2 or more.
|
|
||||||
/// </summary>
|
|
||||||
public static T SingleDefaultOrError<T, TArg1>(this IList<T> list, Action<TArg1> errorAction, TArg1 errorArg1)
|
|
||||||
{
|
|
||||||
Debug.Assert(list != null);
|
|
||||||
Debug.Assert(errorAction != null);
|
|
||||||
|
|
||||||
switch (list.Count)
|
|
||||||
{
|
|
||||||
case 0:
|
|
||||||
return default(T);
|
|
||||||
|
|
||||||
case 1:
|
|
||||||
var value = list[0];
|
|
||||||
return value;
|
|
||||||
|
|
||||||
default:
|
|
||||||
errorAction(errorArg1);
|
|
||||||
return default(T);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns a single value in list matching type TMatch if there is only one, null if there are none of type
|
|
||||||
/// TMatch or calls the errorAction with errorArg1 if there is more than one.
|
|
||||||
/// </summary>
|
|
||||||
public static TMatch SingleOfTypeDefaultOrError<TInput, TMatch, TArg1>(
|
|
||||||
this IList<TInput> list,
|
|
||||||
Action<TArg1> errorAction,
|
|
||||||
TArg1 errorArg1) where TMatch : class
|
|
||||||
{
|
|
||||||
Debug.Assert(list != null);
|
|
||||||
Debug.Assert(errorAction != null);
|
|
||||||
|
|
||||||
TMatch result = null;
|
|
||||||
for (var i = 0; i < list.Count; i++)
|
|
||||||
{
|
|
||||||
var typedValue = list[i] as TMatch;
|
|
||||||
if (typedValue != null)
|
|
||||||
{
|
|
||||||
if (result == null)
|
|
||||||
{
|
|
||||||
result = typedValue;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
errorAction(errorArg1);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Convert an ICollection to an array, removing null values. Fast path for case where there are no null
|
|
||||||
/// values.
|
|
||||||
/// </summary>
|
|
||||||
public static T[] ToArrayWithoutNulls<T>(this ICollection<T> collection) where T : class
|
|
||||||
{
|
|
||||||
Debug.Assert(collection != null);
|
|
||||||
|
|
||||||
var result = new T[collection.Count];
|
|
||||||
var count = 0;
|
|
||||||
foreach (var value in collection)
|
|
||||||
{
|
|
||||||
if (value != null)
|
|
||||||
{
|
|
||||||
result[count] = value;
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (count == collection.Count)
|
|
||||||
{
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var trimmedResult = new T[count];
|
|
||||||
Array.Copy(result, trimmedResult, count);
|
|
||||||
return trimmedResult;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Convert the array to a Dictionary using the keySelector to extract keys from values and the specified
|
|
||||||
/// comparer. Optimized for array input.
|
|
||||||
/// </summary>
|
|
||||||
public static Dictionary<TKey, TValue> ToDictionaryFast<TKey, TValue>(
|
|
||||||
this TValue[] array,
|
|
||||||
Func<TValue, TKey> keySelector,
|
|
||||||
IEqualityComparer<TKey> comparer)
|
|
||||||
{
|
|
||||||
Debug.Assert(array != null);
|
|
||||||
Debug.Assert(keySelector != null);
|
|
||||||
|
|
||||||
var dictionary = new Dictionary<TKey, TValue>(array.Length, comparer);
|
|
||||||
for (var i = 0; i < array.Length; i++)
|
|
||||||
{
|
|
||||||
var value = array[i];
|
|
||||||
dictionary.Add(keySelector(value), value);
|
|
||||||
}
|
|
||||||
return dictionary;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Convert the list to a Dictionary using the keySelector to extract keys from values and the specified
|
|
||||||
/// comparer. Optimized for IList of T input with fast path for array.
|
|
||||||
/// </summary>
|
|
||||||
public static Dictionary<TKey, TValue> ToDictionaryFast<TKey, TValue>(
|
|
||||||
this IList<TValue> list,
|
|
||||||
Func<TValue, TKey> keySelector,
|
|
||||||
IEqualityComparer<TKey> comparer)
|
|
||||||
{
|
|
||||||
Debug.Assert(list != null);
|
|
||||||
Debug.Assert(keySelector != null);
|
|
||||||
|
|
||||||
var array = list as TValue[];
|
|
||||||
if (array != null)
|
|
||||||
{
|
|
||||||
return ToDictionaryFast(array, keySelector, comparer);
|
|
||||||
}
|
|
||||||
return ToDictionaryFastNoCheck(list, keySelector, comparer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Convert the enumerable to a Dictionary using the keySelector to extract keys from values and the specified
|
|
||||||
/// comparer. Fast paths for array and IList of T.
|
|
||||||
/// </summary>
|
|
||||||
public static Dictionary<TKey, TValue> ToDictionaryFast<TKey, TValue>(
|
|
||||||
this IEnumerable<TValue> enumerable,
|
|
||||||
Func<TValue, TKey> keySelector,
|
|
||||||
IEqualityComparer<TKey> comparer)
|
|
||||||
{
|
|
||||||
Debug.Assert(enumerable != null);
|
|
||||||
Debug.Assert(keySelector != null);
|
|
||||||
|
|
||||||
var array = enumerable as TValue[];
|
|
||||||
if (array != null)
|
|
||||||
{
|
|
||||||
return ToDictionaryFast(array, keySelector, comparer);
|
|
||||||
}
|
|
||||||
var list = enumerable as IList<TValue>;
|
|
||||||
if (list != null)
|
|
||||||
{
|
|
||||||
return ToDictionaryFastNoCheck(list, keySelector, comparer);
|
|
||||||
}
|
|
||||||
var dictionary = new Dictionary<TKey, TValue>(comparer);
|
|
||||||
foreach (var value in enumerable)
|
|
||||||
{
|
|
||||||
dictionary.Add(keySelector(value), value);
|
|
||||||
}
|
|
||||||
return dictionary;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Convert the list to a Dictionary using the keySelector to extract keys from values and the specified
|
|
||||||
/// comparer. Optimized for IList of T input. No checking for other types.
|
|
||||||
/// </summary>
|
|
||||||
private static Dictionary<TKey, TValue> ToDictionaryFastNoCheck<TKey, TValue>(
|
|
||||||
IList<TValue> list,
|
|
||||||
Func<TValue, TKey> keySelector,
|
|
||||||
IEqualityComparer<TKey> comparer)
|
|
||||||
{
|
|
||||||
Debug.Assert(list != null);
|
|
||||||
Debug.Assert(keySelector != null);
|
|
||||||
|
|
||||||
var listCount = list.Count;
|
|
||||||
var dictionary = new Dictionary<TKey, TValue>(listCount, comparer);
|
|
||||||
for (var i = 0; i < listCount; i++)
|
|
||||||
{
|
|
||||||
var value = list[i];
|
|
||||||
dictionary.Add(keySelector(value), value);
|
|
||||||
}
|
|
||||||
return dictionary;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Net.Http.Headers;
|
|
||||||
|
|
||||||
namespace System.Net.Http.Formatting
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents the result of content negotiation performed using
|
|
||||||
/// <see cref="IContentNegotiator.Negotiate(Type, HttpRequestMessage, IEnumerable{MediaTypeFormatter})"/>
|
|
||||||
/// </summary>
|
|
||||||
public class ContentNegotiationResult
|
|
||||||
{
|
|
||||||
private MediaTypeFormatter _formatter;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create the content negotiation result object.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="formatter">The formatter.</param>
|
|
||||||
/// <param name="mediaType">The preferred media type. Can be <c>null</c>.</param>
|
|
||||||
public ContentNegotiationResult(MediaTypeFormatter formatter, MediaTypeHeaderValue mediaType)
|
|
||||||
{
|
|
||||||
if (formatter == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(formatter));
|
|
||||||
}
|
|
||||||
|
|
||||||
_formatter = formatter;
|
|
||||||
MediaType = mediaType;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The formatter chosen for serialization.
|
|
||||||
/// </summary>
|
|
||||||
public MediaTypeFormatter Formatter
|
|
||||||
{
|
|
||||||
get { return _formatter; }
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_formatter = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The media type that is associated with the formatter chosen for serialization. Can be <c>null</c>.
|
|
||||||
/// </summary>
|
|
||||||
public MediaTypeHeaderValue MediaType { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,585 +0,0 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net.Http.Headers;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace System.Net.Http.Formatting
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Class that selects a <see cref="MediaTypeFormatter"/> for an <see cref="HttpRequestMessage"/>
|
|
||||||
/// or <see cref="HttpResponseMessage"/>.
|
|
||||||
/// </summary>
|
|
||||||
public class DefaultContentNegotiator : IContentNegotiator
|
|
||||||
{
|
|
||||||
public DefaultContentNegotiator()
|
|
||||||
: this(false)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="DefaultContentNegotiator"/> with
|
|
||||||
/// the given setting for <paramref name="excludeMatchOnTypeOnly"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="excludeMatchOnTypeOnly">
|
|
||||||
/// If ExcludeMatchOnTypeOnly is true then we don't match on type only which means
|
|
||||||
/// that we return null if we can't match on anything in the request. This is useful
|
|
||||||
/// for generating 406 (Not Acceptable) status codes.
|
|
||||||
/// </param>
|
|
||||||
public DefaultContentNegotiator(bool excludeMatchOnTypeOnly)
|
|
||||||
{
|
|
||||||
ExcludeMatchOnTypeOnly = excludeMatchOnTypeOnly;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// If ExcludeMatchOnTypeOnly is true then we don't match on type only which means
|
|
||||||
/// that we return null if we can't match on anything in the request. This is useful
|
|
||||||
/// for generating 406 (Not Acceptable) status codes.
|
|
||||||
/// </summary>
|
|
||||||
public bool ExcludeMatchOnTypeOnly { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Performs content negotiating by selecting the most appropriate <see cref="MediaTypeFormatter"/> out of the
|
|
||||||
/// passed in <paramref name="formatters"/> for the given <paramref name="request"/> that can serialize an
|
|
||||||
/// object of the given <paramref name="type"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="type">The type to be serialized.</param>
|
|
||||||
/// <param name="request">The request.</param>
|
|
||||||
/// <param name="formatters">The set of <see cref="MediaTypeFormatter"/> objects from which to choose.</param>
|
|
||||||
/// <returns>The result of the negotiation containing the most appropriate <see cref="MediaTypeFormatter"/>
|
|
||||||
/// instance, or <c>null</c> if there is no appropriate formatter.</returns>
|
|
||||||
public virtual ContentNegotiationResult Negotiate(
|
|
||||||
Type type,
|
|
||||||
HttpRequestMessage request,
|
|
||||||
IEnumerable<MediaTypeFormatter> formatters)
|
|
||||||
{
|
|
||||||
if (type == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(type));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(request));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (formatters == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(formatters));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Go through each formatter to compute how well it matches.
|
|
||||||
var matches = ComputeFormatterMatches(type, request, formatters);
|
|
||||||
|
|
||||||
// Select best formatter match among the matches
|
|
||||||
var bestFormatterMatch = SelectResponseMediaTypeFormatter(matches);
|
|
||||||
|
|
||||||
// We found a best formatter
|
|
||||||
if (bestFormatterMatch != null)
|
|
||||||
{
|
|
||||||
var bestMediaType = bestFormatterMatch.MediaType;
|
|
||||||
|
|
||||||
// Find the best character encoding for the selected formatter.
|
|
||||||
var bestEncodingMatch = SelectResponseCharacterEncoding(request, bestFormatterMatch.Formatter);
|
|
||||||
if (bestEncodingMatch != null)
|
|
||||||
{
|
|
||||||
// Clone media type value since this is not done defensively in this implementation.
|
|
||||||
// `MediaTypeHeaderValue` lacks a Clone() method in this runtime. Fortunately, this is the only
|
|
||||||
// update to an existing instance we need.
|
|
||||||
var clonedMediaType = new MediaTypeHeaderValue(bestMediaType.MediaType);
|
|
||||||
foreach (var parameter in bestMediaType.Parameters)
|
|
||||||
{
|
|
||||||
clonedMediaType.Parameters.Add(new NameValueHeaderValue(parameter.Name, parameter.Value));
|
|
||||||
}
|
|
||||||
|
|
||||||
bestMediaType = clonedMediaType;
|
|
||||||
bestMediaType.CharSet = bestEncodingMatch.WebName;
|
|
||||||
}
|
|
||||||
|
|
||||||
var bestFormatter =
|
|
||||||
bestFormatterMatch.Formatter.GetPerRequestFormatterInstance(type, request, bestMediaType);
|
|
||||||
return new ContentNegotiationResult(bestFormatter, bestMediaType);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determine how well each formatter matches by associating a <see cref="MediaTypeFormatterMatchRanking"/>
|
|
||||||
/// value with the formatter. Then associate the quality of the match based on q-factors and other parameters.
|
|
||||||
/// The result of this method is a collection of the matches found categorized and assigned a quality value.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="type">The type to be serialized.</param>
|
|
||||||
/// <param name="request">The request.</param>
|
|
||||||
/// <param name="formatters">The set of <see cref="MediaTypeFormatter"/> objects from which to choose.</param>
|
|
||||||
/// <returns>A collection containing all the matches.</returns>
|
|
||||||
protected virtual Collection<MediaTypeFormatterMatch> ComputeFormatterMatches(
|
|
||||||
Type type,
|
|
||||||
HttpRequestMessage request,
|
|
||||||
IEnumerable<MediaTypeFormatter> formatters)
|
|
||||||
{
|
|
||||||
if (type == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(type));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(request));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (formatters == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(formatters));
|
|
||||||
}
|
|
||||||
|
|
||||||
IEnumerable<MediaTypeWithQualityHeaderValue> sortedAcceptValues = null;
|
|
||||||
|
|
||||||
// Go through each formatter to find how well it matches.
|
|
||||||
var matches =
|
|
||||||
new ListWrapperCollection<MediaTypeFormatterMatch>();
|
|
||||||
var writingFormatters = GetWritingFormatters(formatters);
|
|
||||||
for (var i = 0; i < writingFormatters.Length; i++)
|
|
||||||
{
|
|
||||||
var formatter = writingFormatters[i];
|
|
||||||
|
|
||||||
// Check first that formatter can write the actual type
|
|
||||||
if (!formatter.CanWriteType(type))
|
|
||||||
{
|
|
||||||
// Formatter can't even write the type so no match at all
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match against the accept header values.
|
|
||||||
if (sortedAcceptValues == null)
|
|
||||||
{
|
|
||||||
// Sort the Accept header values in descending order based on q-factor
|
|
||||||
sortedAcceptValues = SortMediaTypeWithQualityHeaderValuesByQFactor(request.Headers.Accept);
|
|
||||||
}
|
|
||||||
|
|
||||||
var match = MatchAcceptHeader(sortedAcceptValues, formatter);
|
|
||||||
if (match != null)
|
|
||||||
{
|
|
||||||
matches.Add(match);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match against request's media type if any
|
|
||||||
if ((match = MatchRequestMediaType(request, formatter)) != null)
|
|
||||||
{
|
|
||||||
matches.Add(match);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check whether we should match on type or stop the matching process.
|
|
||||||
// The latter is used to generate 406 (Not Acceptable) status codes.
|
|
||||||
var shouldMatchOnType = ShouldMatchOnType(sortedAcceptValues);
|
|
||||||
|
|
||||||
// Match against the type of object we are writing out
|
|
||||||
if (shouldMatchOnType && (match = MatchType(type, formatter)) != null)
|
|
||||||
{
|
|
||||||
matches.Add(match);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return matches;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Select the best match among the candidate matches found.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="matches">The collection of matches.</param>
|
|
||||||
/// <returns>The <see cref="MediaTypeFormatterMatch"/> determined to be the best match.</returns>
|
|
||||||
protected virtual MediaTypeFormatterMatch SelectResponseMediaTypeFormatter(
|
|
||||||
ICollection<MediaTypeFormatterMatch> matches)
|
|
||||||
{
|
|
||||||
if (matches == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(matches));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Performance-sensitive
|
|
||||||
|
|
||||||
var matchList = matches.AsList();
|
|
||||||
|
|
||||||
MediaTypeFormatterMatch bestMatchOnType = null;
|
|
||||||
MediaTypeFormatterMatch bestMatchOnAcceptHeaderLiteral = null;
|
|
||||||
MediaTypeFormatterMatch bestMatchOnAcceptHeaderSubtypeMediaRange = null;
|
|
||||||
MediaTypeFormatterMatch bestMatchOnAcceptHeaderAllMediaRange = null;
|
|
||||||
MediaTypeFormatterMatch bestMatchOnMediaTypeMapping = null;
|
|
||||||
MediaTypeFormatterMatch bestMatchOnRequestMediaType = null;
|
|
||||||
|
|
||||||
// Go through each formatter to find the best match in each category.
|
|
||||||
for (var i = 0; i < matchList.Count; i++)
|
|
||||||
{
|
|
||||||
var match = matchList[i];
|
|
||||||
switch (match.Ranking)
|
|
||||||
{
|
|
||||||
case MediaTypeFormatterMatchRanking.MatchOnCanWriteType:
|
|
||||||
// First match by type trumps all other type matches
|
|
||||||
if (bestMatchOnType == null)
|
|
||||||
{
|
|
||||||
bestMatchOnType = match;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderLiteral:
|
|
||||||
// Matches on accept headers must choose the highest quality match.
|
|
||||||
// A match of 0.0 means we won't use it at all.
|
|
||||||
bestMatchOnAcceptHeaderLiteral = UpdateBestMatch(bestMatchOnAcceptHeaderLiteral, match);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderSubtypeMediaRange:
|
|
||||||
// Matches on accept headers must choose the highest quality match.
|
|
||||||
// A match of 0.0 means we won't use it at all.
|
|
||||||
bestMatchOnAcceptHeaderSubtypeMediaRange =
|
|
||||||
UpdateBestMatch(bestMatchOnAcceptHeaderSubtypeMediaRange, match);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderAllMediaRange:
|
|
||||||
// Matches on accept headers must choose the highest quality match.
|
|
||||||
// A match of 0.0 means we won't use it at all.
|
|
||||||
bestMatchOnAcceptHeaderAllMediaRange =
|
|
||||||
UpdateBestMatch(bestMatchOnAcceptHeaderAllMediaRange, match);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MediaTypeFormatterMatchRanking.MatchOnRequestMediaType:
|
|
||||||
// First match on request content type trumps other request content matches
|
|
||||||
if (bestMatchOnRequestMediaType == null)
|
|
||||||
{
|
|
||||||
bestMatchOnRequestMediaType = 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 (bestMatchOnMediaTypeMapping != null)
|
|
||||||
{
|
|
||||||
var mappingOverride = bestMatchOnMediaTypeMapping;
|
|
||||||
mappingOverride = UpdateBestMatch(mappingOverride, bestMatchOnAcceptHeaderLiteral);
|
|
||||||
mappingOverride = UpdateBestMatch(mappingOverride, bestMatchOnAcceptHeaderSubtypeMediaRange);
|
|
||||||
mappingOverride = UpdateBestMatch(mappingOverride, bestMatchOnAcceptHeaderAllMediaRange);
|
|
||||||
if (mappingOverride != bestMatchOnMediaTypeMapping)
|
|
||||||
{
|
|
||||||
bestMatchOnMediaTypeMapping = 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
|
|
||||||
MediaTypeFormatterMatch bestMatch = null;
|
|
||||||
if (bestMatchOnMediaTypeMapping != null)
|
|
||||||
{
|
|
||||||
bestMatch = bestMatchOnMediaTypeMapping;
|
|
||||||
}
|
|
||||||
else if (bestMatchOnAcceptHeaderLiteral != null ||
|
|
||||||
bestMatchOnAcceptHeaderSubtypeMediaRange != null ||
|
|
||||||
bestMatchOnAcceptHeaderAllMediaRange != null)
|
|
||||||
{
|
|
||||||
bestMatch = UpdateBestMatch(bestMatch, bestMatchOnAcceptHeaderLiteral);
|
|
||||||
bestMatch = UpdateBestMatch(bestMatch, bestMatchOnAcceptHeaderSubtypeMediaRange);
|
|
||||||
bestMatch = UpdateBestMatch(bestMatch, bestMatchOnAcceptHeaderAllMediaRange);
|
|
||||||
}
|
|
||||||
else if (bestMatchOnRequestMediaType != null)
|
|
||||||
{
|
|
||||||
bestMatch = bestMatchOnRequestMediaType;
|
|
||||||
}
|
|
||||||
else if (bestMatchOnType != null)
|
|
||||||
{
|
|
||||||
bestMatch = bestMatchOnType;
|
|
||||||
}
|
|
||||||
|
|
||||||
return bestMatch;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determine the best character encoding for writing the response. First we look
|
|
||||||
/// for accept-charset headers and if not found then we try to match
|
|
||||||
/// any charset encoding in the request (in case of PUT, POST, etc.)
|
|
||||||
/// If no encoding is found then we use the default for the formatter.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The <see cref="Encoding"/> determined to be the best match.</returns>
|
|
||||||
protected virtual Encoding SelectResponseCharacterEncoding(
|
|
||||||
HttpRequestMessage request,
|
|
||||||
MediaTypeFormatter formatter)
|
|
||||||
{
|
|
||||||
if (request == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(request));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (formatter == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(formatter));
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there are any SupportedEncodings then we pick an encoding
|
|
||||||
var supportedEncodings = formatter.SupportedEncodings.ToList();
|
|
||||||
if (supportedEncodings.Count > 0)
|
|
||||||
{
|
|
||||||
// Sort Accept-Charset header values
|
|
||||||
var sortedAcceptCharsetValues =
|
|
||||||
SortStringWithQualityHeaderValuesByQFactor(request.Headers.AcceptCharset);
|
|
||||||
|
|
||||||
// Check for match based on accept-charset headers
|
|
||||||
foreach (StringWithQualityHeaderValue acceptCharset in sortedAcceptCharsetValues)
|
|
||||||
{
|
|
||||||
for (var i = 0; i < supportedEncodings.Count; i++)
|
|
||||||
{
|
|
||||||
var encoding = supportedEncodings[i];
|
|
||||||
if (encoding != null && acceptCharset.Quality != FormattingUtilities.NoMatch &&
|
|
||||||
(acceptCharset.Value.Equals(encoding.WebName, StringComparison.OrdinalIgnoreCase) ||
|
|
||||||
acceptCharset.Value.Equals("*", StringComparison.Ordinal)))
|
|
||||||
{
|
|
||||||
return encoding;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for match based on any request entity body
|
|
||||||
return formatter.SelectCharacterEncoding(request.Content != null ? request.Content.Headers : null);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Match the request accept header field values against the formatter's registered supported media types.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="sortedAcceptValues">The sorted accept header values to match.</param>
|
|
||||||
/// <param name="formatter">The formatter to match against.</param>
|
|
||||||
/// <returns>
|
|
||||||
/// A <see cref="MediaTypeFormatterMatch"/> indicating the quality of the match or null is no match.
|
|
||||||
/// </returns>
|
|
||||||
protected virtual MediaTypeFormatterMatch MatchAcceptHeader(
|
|
||||||
IEnumerable<MediaTypeWithQualityHeaderValue> sortedAcceptValues,
|
|
||||||
MediaTypeFormatter formatter)
|
|
||||||
{
|
|
||||||
if (sortedAcceptValues == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(sortedAcceptValues));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (formatter == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(formatter));
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (MediaTypeWithQualityHeaderValue acceptMediaTypeValue in sortedAcceptValues)
|
|
||||||
{
|
|
||||||
var supportedMediaTypes = formatter.SupportedMediaTypes.ToList();
|
|
||||||
for (var i = 0; i < supportedMediaTypes.Count; i++)
|
|
||||||
{
|
|
||||||
var supportedMediaType = supportedMediaTypes[i];
|
|
||||||
MediaTypeFormatterMatchRanking ranking;
|
|
||||||
if (supportedMediaType != null &&
|
|
||||||
acceptMediaTypeValue.Quality != FormattingUtilities.NoMatch &&
|
|
||||||
supportedMediaType.IsSubsetOf(acceptMediaTypeValue, out ranking))
|
|
||||||
{
|
|
||||||
return new MediaTypeFormatterMatch(
|
|
||||||
formatter,
|
|
||||||
supportedMediaType,
|
|
||||||
acceptMediaTypeValue.Quality,
|
|
||||||
ranking);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Match any request media type (in case there is a request entity body) against the formatter's registered
|
|
||||||
/// media types.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="request">The request to match.</param>
|
|
||||||
/// <param name="formatter">The formatter to match against.</param>
|
|
||||||
/// <returns>
|
|
||||||
/// A <see cref="MediaTypeFormatterMatch"/> indicating the quality of the match or null is no match.
|
|
||||||
/// </returns>
|
|
||||||
protected virtual MediaTypeFormatterMatch MatchRequestMediaType(
|
|
||||||
HttpRequestMessage request,
|
|
||||||
MediaTypeFormatter formatter)
|
|
||||||
{
|
|
||||||
if (request == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(request));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (formatter == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(formatter));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.Content != null)
|
|
||||||
{
|
|
||||||
var requestMediaType = request.Content.Headers.ContentType;
|
|
||||||
if (requestMediaType != null)
|
|
||||||
{
|
|
||||||
var supportedMediaTypes = formatter.SupportedMediaTypes.ToList();
|
|
||||||
for (var i = 0; i < supportedMediaTypes.Count; i++)
|
|
||||||
{
|
|
||||||
var supportedMediaType = supportedMediaTypes[i];
|
|
||||||
if (supportedMediaType != null && supportedMediaType.IsSubsetOf(requestMediaType))
|
|
||||||
{
|
|
||||||
return new MediaTypeFormatterMatch(
|
|
||||||
formatter,
|
|
||||||
supportedMediaType,
|
|
||||||
FormattingUtilities.Match,
|
|
||||||
MediaTypeFormatterMatchRanking.MatchOnRequestMediaType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determine whether to match on type or not. This is used to determine whether to
|
|
||||||
/// generate a 406 response or use the default media type formatter in case there
|
|
||||||
/// is no match against anything in the request. If ExcludeMatchOnTypeOnly is true
|
|
||||||
/// then we don't match on type unless there are no accept headers.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="sortedAcceptValues">The sorted accept header values to match.</param>
|
|
||||||
/// <returns>
|
|
||||||
/// True if not ExcludeMatchOnTypeOnly and accept headers with a q-factor bigger than 0.0 are present.
|
|
||||||
/// </returns>
|
|
||||||
protected virtual bool ShouldMatchOnType(
|
|
||||||
IEnumerable<MediaTypeWithQualityHeaderValue> sortedAcceptValues)
|
|
||||||
{
|
|
||||||
if (sortedAcceptValues == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(sortedAcceptValues));
|
|
||||||
}
|
|
||||||
|
|
||||||
return !(ExcludeMatchOnTypeOnly && sortedAcceptValues.Any());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Pick the first supported media type and indicate we've matched only on type
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="type">The type to be serialized.</param>
|
|
||||||
/// <param name="formatter">The formatter we are matching against.</param>
|
|
||||||
/// <returns>
|
|
||||||
/// A <see cref="MediaTypeFormatterMatch"/> indicating the quality of the match or null is no match.
|
|
||||||
/// </returns>
|
|
||||||
protected virtual MediaTypeFormatterMatch MatchType(
|
|
||||||
Type type,
|
|
||||||
MediaTypeFormatter formatter)
|
|
||||||
{
|
|
||||||
if (type == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(type));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (formatter == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(formatter));
|
|
||||||
}
|
|
||||||
|
|
||||||
// We already know that we do match on type -- otherwise we wouldn't even be called --
|
|
||||||
// so this is just a matter of determining how we match.
|
|
||||||
MediaTypeHeaderValue mediaType = null;
|
|
||||||
var supportedMediaTypes = formatter.SupportedMediaTypes.ToList();
|
|
||||||
if (supportedMediaTypes.Count > 0)
|
|
||||||
{
|
|
||||||
mediaType = supportedMediaTypes[0];
|
|
||||||
}
|
|
||||||
return new MediaTypeFormatterMatch(
|
|
||||||
formatter,
|
|
||||||
mediaType,
|
|
||||||
FormattingUtilities.Match,
|
|
||||||
MediaTypeFormatterMatchRanking.MatchOnCanWriteType);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sort Accept header values and related header field values with similar syntax rules
|
|
||||||
/// (if more than 1) in descending order based on q-factor.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="headerValues">The header values to sort.</param>
|
|
||||||
/// <returns>The sorted header values.</returns>
|
|
||||||
protected virtual IEnumerable<MediaTypeWithQualityHeaderValue> SortMediaTypeWithQualityHeaderValuesByQFactor(
|
|
||||||
ICollection<MediaTypeWithQualityHeaderValue> headerValues)
|
|
||||||
{
|
|
||||||
if (headerValues.Count > 1)
|
|
||||||
{
|
|
||||||
// Use OrderBy() instead of Array.Sort() as it performs fewer comparisons. In this case the comparisons
|
|
||||||
// are quite expensive so OrderBy() performs better.
|
|
||||||
return headerValues
|
|
||||||
.OrderByDescending(m => m, MediaTypeWithQualityHeaderValueComparer.QualityComparer)
|
|
||||||
.ToArray();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return headerValues;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sort Accept-Charset, Accept-Encoding, Accept-Language and related header field values with similar syntax
|
|
||||||
/// rules (if more than 1) in descending order based on q-factor.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="headerValues">The header values to sort.</param>
|
|
||||||
/// <returns>The sorted header values.</returns>
|
|
||||||
protected virtual IEnumerable<StringWithQualityHeaderValue> SortStringWithQualityHeaderValuesByQFactor(
|
|
||||||
ICollection<StringWithQualityHeaderValue> headerValues)
|
|
||||||
{
|
|
||||||
if (headerValues == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(headerValues));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (headerValues.Count > 1)
|
|
||||||
{
|
|
||||||
// Use OrderBy() instead of Array.Sort() as it performs fewer comparisons. In this case the comparisons
|
|
||||||
// are quite expensive so OrderBy() performs better.
|
|
||||||
return headerValues
|
|
||||||
.OrderByDescending(m => m, StringWithQualityHeaderValueComparer.QualityComparer)
|
|
||||||
.ToArray();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return headerValues;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Evaluates whether a match is better than the current match and if so returns the replacement; otherwise
|
|
||||||
/// returns the current match.
|
|
||||||
/// </summary>
|
|
||||||
protected virtual MediaTypeFormatterMatch UpdateBestMatch(
|
|
||||||
MediaTypeFormatterMatch current,
|
|
||||||
MediaTypeFormatterMatch potentialReplacement)
|
|
||||||
{
|
|
||||||
if (potentialReplacement == null)
|
|
||||||
{
|
|
||||||
return current;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (current != null)
|
|
||||||
{
|
|
||||||
return (potentialReplacement.Quality > current.Quality) ? potentialReplacement : current;
|
|
||||||
}
|
|
||||||
|
|
||||||
return potentialReplacement;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static MediaTypeFormatter[] GetWritingFormatters(IEnumerable<MediaTypeFormatter> formatters)
|
|
||||||
{
|
|
||||||
Debug.Assert(formatters != null);
|
|
||||||
return formatters.AsArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
using Microsoft.AspNetCore.WebUtilities;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace System.Net.Http.Formatting
|
|
||||||
{
|
|
||||||
public class FormDataCollection : IEnumerable<KeyValuePair<string, string>>
|
|
||||||
{
|
|
||||||
private readonly IList<KeyValuePair<string, string>> _values;
|
|
||||||
|
|
||||||
public FormDataCollection(string query)
|
|
||||||
{
|
|
||||||
var parsedQuery = QueryHelpers.ParseQuery(query);
|
|
||||||
|
|
||||||
var values = new List<KeyValuePair<string, string>>();
|
|
||||||
foreach (var kvp in parsedQuery)
|
|
||||||
{
|
|
||||||
foreach (var value in kvp.Value)
|
|
||||||
{
|
|
||||||
values.Add(new KeyValuePair<string, string>(kvp.Key, value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_values = values;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
|
|
||||||
{
|
|
||||||
return _values.GetEnumerator();
|
|
||||||
}
|
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator()
|
|
||||||
{
|
|
||||||
return _values.GetEnumerator();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,236 +0,0 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net.Http.Formatting;
|
|
||||||
using System.Net.Http.Headers;
|
|
||||||
using System.Xml;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
|
|
||||||
namespace System.Net.Http
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Provides various internal utility functions
|
|
||||||
/// </summary>
|
|
||||||
internal static class FormattingUtilities
|
|
||||||
{
|
|
||||||
// Supported date formats for input.
|
|
||||||
private static readonly string[] dateFormats = new string[]
|
|
||||||
{
|
|
||||||
// "r", // RFC 1123, required output format but too strict for input
|
|
||||||
"ddd, d MMM yyyy H:m:s 'GMT'", // RFC 1123 (r, except it allows both 1 and 01 for date and time)
|
|
||||||
"ddd, d MMM yyyy H:m:s", // RFC 1123, no zone - assume GMT
|
|
||||||
"d MMM yyyy H:m:s 'GMT'", // RFC 1123, no day-of-week
|
|
||||||
"d MMM yyyy H:m:s", // RFC 1123, no day-of-week, no zone
|
|
||||||
"ddd, d MMM yy H:m:s 'GMT'", // RFC 1123, short year
|
|
||||||
"ddd, d MMM yy H:m:s", // RFC 1123, short year, no zone
|
|
||||||
"d MMM yy H:m:s 'GMT'", // RFC 1123, no day-of-week, short year
|
|
||||||
"d MMM yy H:m:s", // RFC 1123, no day-of-week, short year, no zone
|
|
||||||
|
|
||||||
"dddd, d'-'MMM'-'yy H:m:s 'GMT'", // RFC 850
|
|
||||||
"dddd, d'-'MMM'-'yy H:m:s", // RFC 850 no zone
|
|
||||||
"ddd MMM d H:m:s yyyy", // ANSI C's asctime() format
|
|
||||||
|
|
||||||
"ddd, d MMM yyyy H:m:s zzz", // RFC 5322
|
|
||||||
"ddd, d MMM yyyy H:m:s", // RFC 5322 no zone
|
|
||||||
"d MMM yyyy H:m:s zzz", // RFC 5322 no day-of-week
|
|
||||||
"d MMM yyyy H:m:s", // RFC 5322 no day-of-week, no zone
|
|
||||||
};
|
|
||||||
|
|
||||||
// Valid header token characters are within the range 0x20 < c < 0x7F excluding the following characters
|
|
||||||
private const string NonTokenChars = "()<>@,;:\\\"/[]?={}";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Quality factor to indicate a perfect match.
|
|
||||||
/// </summary>
|
|
||||||
public const double Match = 1.0;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Quality factor to indicate no match.
|
|
||||||
/// </summary>
|
|
||||||
public const double NoMatch = 0.0;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The default max depth for our formatter is 256
|
|
||||||
/// </summary>
|
|
||||||
public const int DefaultMaxDepth = 256;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The default min depth for our formatter is 1
|
|
||||||
/// </summary>
|
|
||||||
public const int DefaultMinDepth = 1;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// HTTP X-Requested-With header field name
|
|
||||||
/// </summary>
|
|
||||||
public const string HttpRequestedWithHeader = "x-requested-with";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// HTTP X-Requested-With header field value
|
|
||||||
/// </summary>
|
|
||||||
public const string HttpRequestedWithHeaderValue = "XMLHttpRequest";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// HTTP Host header field name
|
|
||||||
/// </summary>
|
|
||||||
public const string HttpHostHeader = "Host";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// HTTP Version token
|
|
||||||
/// </summary>
|
|
||||||
public const string HttpVersionToken = "HTTP";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A <see cref="Type"/> representing <see cref="HttpRequestMessage"/>.
|
|
||||||
/// </summary>
|
|
||||||
public static readonly Type HttpRequestMessageType = typeof(HttpRequestMessage);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A <see cref="Type"/> representing <see cref="HttpResponseMessage"/>.
|
|
||||||
/// </summary>
|
|
||||||
public static readonly Type HttpResponseMessageType = typeof(HttpResponseMessage);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A <see cref="Type"/> representing <see cref="HttpContent"/>.
|
|
||||||
/// </summary>
|
|
||||||
public static readonly Type HttpContentType = typeof(HttpContent);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A <see cref="Type"/> representing <see cref="DelegatingEnumerable{T}"/>.
|
|
||||||
/// </summary>
|
|
||||||
public static readonly Type DelegatingEnumerableGenericType = typeof(DelegatingEnumerable<>);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A <see cref="Type"/> representing <see cref="IEnumerable{T}"/>.
|
|
||||||
/// </summary>
|
|
||||||
public static readonly Type EnumerableInterfaceGenericType = typeof(IEnumerable<>);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A <see cref="Type"/> representing <see cref="IQueryable{T}"/>.
|
|
||||||
/// </summary>
|
|
||||||
public static readonly Type QueryableInterfaceGenericType = typeof(IQueryable<>);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determines whether <paramref name="type"/> is a <see cref="JToken"/> type.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="type">The type to test.</param>
|
|
||||||
/// <returns>
|
|
||||||
/// <c>true</c> if <paramref name="type"/> is a <see cref="JToken"/> type; otherwise, <c>false</c>.
|
|
||||||
/// </returns>
|
|
||||||
public static bool IsJTokenType(Type type)
|
|
||||||
{
|
|
||||||
return typeof(JToken).IsAssignableFrom(type);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates an empty <see cref="HttpContentHeaders"/> instance. The only way is to get it from a dummy
|
|
||||||
/// <see cref="HttpContent"/> instance.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The created instance.</returns>
|
|
||||||
public static HttpContentHeaders CreateEmptyContentHeaders()
|
|
||||||
{
|
|
||||||
HttpContent tempContent = null;
|
|
||||||
HttpContentHeaders contentHeaders;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
tempContent = new StringContent(string.Empty);
|
|
||||||
contentHeaders = tempContent.Headers;
|
|
||||||
contentHeaders.Clear();
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
// We can dispose the content without touching the headers
|
|
||||||
if (tempContent != null)
|
|
||||||
{
|
|
||||||
tempContent.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return contentHeaders;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create a default reader quotas with a default depth quota of 1K.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>A default <see cref="XmlDictionaryReaderQuotas"/> with a default depth quota of 1K.</returns>
|
|
||||||
public static XmlDictionaryReaderQuotas CreateDefaultReaderQuotas()
|
|
||||||
{
|
|
||||||
// MaxDepth is a DOS mitigation. We don't support MaxDepth in portable libraries because it is strictly
|
|
||||||
// client side.
|
|
||||||
return new XmlDictionaryReaderQuotas()
|
|
||||||
{
|
|
||||||
MaxArrayLength = int.MaxValue,
|
|
||||||
MaxBytesPerRead = int.MaxValue,
|
|
||||||
MaxDepth = DefaultMaxDepth,
|
|
||||||
MaxNameTableCharCount = int.MaxValue,
|
|
||||||
MaxStringContentLength = int.MaxValue
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Remove bounding quotes on a token if present
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="token">Token to unquote.</param>
|
|
||||||
/// <returns>Unquoted token.</returns>
|
|
||||||
public static string UnquoteToken(string token)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(token))
|
|
||||||
{
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (token.StartsWith("\"", StringComparison.Ordinal) &&
|
|
||||||
token.EndsWith("\"", StringComparison.Ordinal) &&
|
|
||||||
token.Length > 1)
|
|
||||||
{
|
|
||||||
return token.Substring(1, token.Length - 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool ValidateHeaderToken(string token)
|
|
||||||
{
|
|
||||||
if (token == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (char c in token)
|
|
||||||
{
|
|
||||||
if (c < 0x21 || c > 0x7E || NonTokenChars.IndexOf(c) != -1)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string DateToString(DateTimeOffset dateTime)
|
|
||||||
{
|
|
||||||
// Format according to RFC1123; 'r' uses invariant info (DateTimeFormatInfo.InvariantInfo)
|
|
||||||
return dateTime.ToUniversalTime().ToString("r", CultureInfo.InvariantCulture);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool TryParseDate(string input, out DateTimeOffset result)
|
|
||||||
{
|
|
||||||
return DateTimeOffset.TryParseExact(input, dateFormats, DateTimeFormatInfo.InvariantInfo,
|
|
||||||
DateTimeStyles.AllowWhiteSpaces | DateTimeStyles.AssumeUniversal,
|
|
||||||
out result);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Parses valid integer strings with no leading signs, whitespace or other <see cref="NumberStyles"/>
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="value">The value to parse</param>
|
|
||||||
/// <param name="result">The result</param>
|
|
||||||
/// <returns>True if value was valid; false otherwise.</returns>
|
|
||||||
public static bool TryParseInt32(string value, out int result)
|
|
||||||
{
|
|
||||||
return int.TryParse(value, NumberStyles.None, NumberFormatInfo.InvariantInfo, out result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace System.Net.Http.Formatting
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Performs content negotiation.
|
|
||||||
/// This is the process of selecting a response writer (formatter) in compliance with header values in the request.
|
|
||||||
/// </summary>
|
|
||||||
public interface IContentNegotiator
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Performs content negotiating by selecting the most appropriate <see cref="MediaTypeFormatter"/> out of the
|
|
||||||
/// passed in <paramref name="formatters"/> for the given <paramref name="request"/> that can serialize an
|
|
||||||
/// object of the given <paramref name="type"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// Implementations of this method should call <see cref="MediaTypeFormatter.GetPerRequestFormatterInstance"/>
|
|
||||||
/// on the selected <see cref="MediaTypeFormatter">formatter</see> and return the result of that method.
|
|
||||||
/// </remarks>
|
|
||||||
/// <param name="type">The type to be serialized.</param>
|
|
||||||
/// <param name="request">
|
|
||||||
/// Request message, which contains the header values used to perform negotiation.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="formatters">The set of <see cref="MediaTypeFormatter"/> objects from which to choose.</param>
|
|
||||||
/// <returns>
|
|
||||||
/// The result of the negotiation containing the most appropriate <see cref="MediaTypeFormatter"/> instance,
|
|
||||||
/// or <c>null</c> if there is no appropriate formatter.
|
|
||||||
/// </returns>
|
|
||||||
ContentNegotiationResult Negotiate(
|
|
||||||
Type type,
|
|
||||||
HttpRequestMessage request,
|
|
||||||
IEnumerable<MediaTypeFormatter> formatters);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace System.Collections.ObjectModel
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A class that inherits from Collection of T but also exposes its underlying data as List of T for performance.
|
|
||||||
/// </summary>
|
|
||||||
internal sealed class ListWrapperCollection<T> : Collection<T>
|
|
||||||
{
|
|
||||||
private readonly List<T> _items;
|
|
||||||
|
|
||||||
internal ListWrapperCollection()
|
|
||||||
: this(new List<T>())
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
internal ListWrapperCollection(List<T> list)
|
|
||||||
: base(list)
|
|
||||||
{
|
|
||||||
_items = list;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal List<T> ItemsList
|
|
||||||
{
|
|
||||||
get { return _items; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,109 +0,0 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
using System.Net.Http.Headers;
|
|
||||||
|
|
||||||
namespace System.Net.Http.Formatting
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Constants related to media types.
|
|
||||||
/// </summary>
|
|
||||||
internal static class MediaTypeConstants
|
|
||||||
{
|
|
||||||
private static readonly MediaTypeHeaderValue _defaultApplicationXmlMediaType =
|
|
||||||
new MediaTypeHeaderValue("application/xml");
|
|
||||||
private static readonly MediaTypeHeaderValue _defaultTextXmlMediaType =
|
|
||||||
new MediaTypeHeaderValue("text/xml");
|
|
||||||
private static readonly MediaTypeHeaderValue _defaultApplicationJsonMediaType =
|
|
||||||
new MediaTypeHeaderValue("application/json");
|
|
||||||
private static readonly MediaTypeHeaderValue _defaultTextJsonMediaType =
|
|
||||||
new MediaTypeHeaderValue("text/json");
|
|
||||||
private static readonly MediaTypeHeaderValue _defaultApplicationOctetStreamMediaType =
|
|
||||||
new MediaTypeHeaderValue("application/octet-stream");
|
|
||||||
private static readonly MediaTypeHeaderValue _defaultApplicationFormUrlEncodedMediaType =
|
|
||||||
new MediaTypeHeaderValue("application/x-www-form-urlencoded");
|
|
||||||
private static readonly MediaTypeHeaderValue _defaultApplicationBsonMediaType =
|
|
||||||
new MediaTypeHeaderValue("application/bson");
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a <see cref="MediaTypeHeaderValue"/> instance representing <c>application/octet-stream</c>.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>
|
|
||||||
/// A new <see cref="MediaTypeHeaderValue"/> instance representing <c>application/octet-stream</c>.
|
|
||||||
/// </value>
|
|
||||||
public static MediaTypeHeaderValue ApplicationOctetStreamMediaType
|
|
||||||
{
|
|
||||||
get { return _defaultApplicationOctetStreamMediaType; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a <see cref="MediaTypeHeaderValue"/> instance representing <c>application/xml</c>.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>
|
|
||||||
/// A new <see cref="MediaTypeHeaderValue"/> instance representing <c>application/xml</c>.
|
|
||||||
/// </value>
|
|
||||||
public static MediaTypeHeaderValue ApplicationXmlMediaType
|
|
||||||
{
|
|
||||||
get { return _defaultApplicationXmlMediaType; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a <see cref="MediaTypeHeaderValue"/> instance representing <c>application/json</c>.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>
|
|
||||||
/// A new <see cref="MediaTypeHeaderValue"/> instance representing <c>application/json</c>.
|
|
||||||
/// </value>
|
|
||||||
public static MediaTypeHeaderValue ApplicationJsonMediaType
|
|
||||||
{
|
|
||||||
get { return _defaultApplicationJsonMediaType; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a <see cref="MediaTypeHeaderValue"/> instance representing <c>text/xml</c>.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>
|
|
||||||
/// A new <see cref="MediaTypeHeaderValue"/> instance representing <c>text/xml</c>.
|
|
||||||
/// </value>
|
|
||||||
public static MediaTypeHeaderValue TextXmlMediaType
|
|
||||||
{
|
|
||||||
get { return _defaultTextXmlMediaType; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a <see cref="MediaTypeHeaderValue"/> instance representing <c>text/json</c>.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>
|
|
||||||
/// A new <see cref="MediaTypeHeaderValue"/> instance representing <c>text/json</c>.
|
|
||||||
/// </value>
|
|
||||||
public static MediaTypeHeaderValue TextJsonMediaType
|
|
||||||
{
|
|
||||||
get { return _defaultTextJsonMediaType; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a <see cref="MediaTypeHeaderValue"/> instance representing <c>application/x-www-form-urlencoded</c>.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>
|
|
||||||
/// A new <see cref="MediaTypeHeaderValue"/> instance representing <c>application/x-www-form-urlencoded</c>.
|
|
||||||
/// </value>
|
|
||||||
public static MediaTypeHeaderValue ApplicationFormUrlEncodedMediaType
|
|
||||||
{
|
|
||||||
get { return _defaultApplicationFormUrlEncodedMediaType; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a <see cref="MediaTypeHeaderValue"/> instance representing <c>application/bson</c>.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>
|
|
||||||
/// A new <see cref="MediaTypeHeaderValue"/> instance representing <c>application/bson</c>.
|
|
||||||
/// </value>
|
|
||||||
/// <remarks>
|
|
||||||
/// Not yet a standard. In particular this media type is not currently listed at
|
|
||||||
/// https://www.iana.org/assignments/media-types/application.
|
|
||||||
/// </remarks>
|
|
||||||
public static MediaTypeHeaderValue ApplicationBsonMediaType
|
|
||||||
{
|
|
||||||
get { return _defaultApplicationBsonMediaType; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
using System.Net.Http.Headers;
|
|
||||||
|
|
||||||
namespace System.Net.Http.Formatting
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// This class describes how well a particular <see cref="MediaTypeFormatter"/> matches a request.
|
|
||||||
/// </summary>
|
|
||||||
public class MediaTypeFormatterMatch
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="MediaTypeFormatterMatch"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="formatter">The matching formatter.</param>
|
|
||||||
/// <param name="mediaType">
|
|
||||||
/// The media type. Can be <c>null</c> in which case the media type <c>application/octet-stream</c> is used.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="quality">
|
|
||||||
/// The quality of the match. Can be <c>null</c> in which case it is considered a full match with a value of
|
|
||||||
/// 1.0.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="ranking">The kind of match.</param>
|
|
||||||
public MediaTypeFormatterMatch(
|
|
||||||
MediaTypeFormatter formatter,
|
|
||||||
MediaTypeHeaderValue mediaType,
|
|
||||||
double? quality,
|
|
||||||
MediaTypeFormatterMatchRanking ranking)
|
|
||||||
{
|
|
||||||
Formatter = formatter;
|
|
||||||
MediaType = mediaType != null ? mediaType : MediaTypeConstants.ApplicationOctetStreamMediaType;
|
|
||||||
Quality = quality ?? FormattingUtilities.Match;
|
|
||||||
Ranking = ranking;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the media type formatter.
|
|
||||||
/// </summary>
|
|
||||||
public MediaTypeFormatter Formatter { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the matched media type.
|
|
||||||
/// </summary>
|
|
||||||
public MediaTypeHeaderValue MediaType { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the quality of the match
|
|
||||||
/// </summary>
|
|
||||||
public double Quality { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the kind of match that occurred.
|
|
||||||
/// </summary>
|
|
||||||
public MediaTypeFormatterMatchRanking Ranking { get; private set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
namespace System.Net.Http.Formatting
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Contains information about the degree to which a <see cref="MediaTypeFormatter"/> matches the
|
|
||||||
/// explicit or implicit preferences found in an incoming request.
|
|
||||||
/// </summary>
|
|
||||||
public enum MediaTypeFormatterMatchRanking
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// No match was found
|
|
||||||
/// </summary>
|
|
||||||
None = 0,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Matched on type meaning that the formatter is able to serialize the type
|
|
||||||
/// </summary>
|
|
||||||
MatchOnCanWriteType,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Matched on explicit literal accept header in <see cref="HttpRequestMessage"/>,
|
|
||||||
/// e.g. "application/json".
|
|
||||||
/// </summary>
|
|
||||||
MatchOnRequestAcceptHeaderLiteral,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Matched on explicit subtype range accept header in <see cref="HttpRequestMessage"/>,
|
|
||||||
/// e.g. "application/*".
|
|
||||||
/// </summary>
|
|
||||||
MatchOnRequestAcceptHeaderSubtypeMediaRange,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Matched on explicit all media type range accept header in <see cref="HttpRequestMessage"/>,
|
|
||||||
/// e.g. "*/*"
|
|
||||||
/// </summary>
|
|
||||||
MatchOnRequestAcceptHeaderAllMediaRange,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Matched on the media type of the <see cref="HttpContent"/> of the <see cref="HttpRequestMessage"/>.
|
|
||||||
/// </summary>
|
|
||||||
MatchOnRequestMediaType,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,117 +0,0 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Net.Http.Headers;
|
|
||||||
|
|
||||||
namespace System.Net.Http.Formatting
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Extension methods for <see cref="MediaTypeHeaderValue"/>.
|
|
||||||
/// </summary>
|
|
||||||
internal static class MediaTypeHeaderValueExtensions
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Determines whether two <see cref="MediaTypeHeaderValue"/> instances match. The instance
|
|
||||||
/// <paramref name="mediaType1"/> is said to match <paramref name="mediaType2"/> if and only if
|
|
||||||
/// <paramref name="mediaType1"/> is a strict subset of the values and parameters of
|
|
||||||
/// <paramref name="mediaType2"/>.
|
|
||||||
/// That is, if the media type and media type parameters of <paramref name="mediaType1"/> are all present
|
|
||||||
/// and match those of <paramref name="mediaType2"/> then it is a match even though
|
|
||||||
/// <paramref name="mediaType2"/> may have additional parameters.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="mediaType1">The first media type.</param>
|
|
||||||
/// <param name="mediaType2">The second media type.</param>
|
|
||||||
/// <returns><c>true</c> if this is a subset of <paramref name="mediaType2"/>; false otherwise.</returns>
|
|
||||||
public static bool IsSubsetOf(this MediaTypeHeaderValue mediaType1, MediaTypeHeaderValue mediaType2)
|
|
||||||
{
|
|
||||||
MediaTypeFormatterMatchRanking mediaType2Range;
|
|
||||||
return IsSubsetOf(mediaType1, mediaType2, out mediaType2Range);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determines whether two <see cref="MediaTypeHeaderValue"/> instances match. The instance
|
|
||||||
/// <paramref name="mediaType1"/> is said to match <paramref name="mediaType2"/> if and only if
|
|
||||||
/// <paramref name="mediaType1"/> is a strict subset of the values and parameters of
|
|
||||||
/// <paramref name="mediaType2"/>.
|
|
||||||
/// That is, if the media type and media type parameters of <paramref name="mediaType1"/> are all present
|
|
||||||
/// and match those of <paramref name="mediaType2"/> then it is a match even though
|
|
||||||
/// <paramref name="mediaType2"/> may have additional parameters.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="mediaType1">The first media type.</param>
|
|
||||||
/// <param name="mediaType2">The second media type.</param>
|
|
||||||
/// <param name="mediaType2Range">
|
|
||||||
/// Indicates whether <paramref name="mediaType2"/> is a regular media type, a subtype media range, or a full
|
|
||||||
/// media range.
|
|
||||||
/// </param>
|
|
||||||
/// <returns><c>true</c> if this is a subset of <paramref name="mediaType2"/>; false otherwise.</returns>
|
|
||||||
public static bool IsSubsetOf(
|
|
||||||
this MediaTypeHeaderValue mediaType1,
|
|
||||||
MediaTypeHeaderValue mediaType2,
|
|
||||||
out MediaTypeFormatterMatchRanking mediaType2Range)
|
|
||||||
{
|
|
||||||
// Performance-sensitive
|
|
||||||
Debug.Assert(mediaType1 != null);
|
|
||||||
|
|
||||||
if (mediaType2 == null)
|
|
||||||
{
|
|
||||||
mediaType2Range = MediaTypeFormatterMatchRanking.None;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var parsedMediaType1 = new ParsedMediaTypeHeaderValue(mediaType1);
|
|
||||||
var parsedMediaType2 = new ParsedMediaTypeHeaderValue(mediaType2);
|
|
||||||
mediaType2Range = parsedMediaType2.IsAllMediaRange ? MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderAllMediaRange :
|
|
||||||
parsedMediaType2.IsSubtypeMediaRange ? MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderSubtypeMediaRange :
|
|
||||||
MediaTypeFormatterMatchRanking.None;
|
|
||||||
|
|
||||||
if (!parsedMediaType1.TypesEqual(ref parsedMediaType2))
|
|
||||||
{
|
|
||||||
if (mediaType2Range != MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderAllMediaRange)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (!parsedMediaType1.SubTypesEqual(ref parsedMediaType2))
|
|
||||||
{
|
|
||||||
if (mediaType2Range != MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderSubtypeMediaRange)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mediaType2Range = MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderLiteral;
|
|
||||||
}
|
|
||||||
|
|
||||||
// So far we either have a full match or a subset match. Now check that all of
|
|
||||||
// mediaType1's parameters are present and equal in mediatype2
|
|
||||||
// Optimize for the common case where the parameters inherit from Collection<T> and cache the count which
|
|
||||||
// is faster for Collection<T>.
|
|
||||||
var parameters1 = mediaType1.Parameters.AsCollection();
|
|
||||||
var parameterCount1 = parameters1.Count;
|
|
||||||
var parameters2 = mediaType2.Parameters.AsCollection();
|
|
||||||
var parameterCount2 = parameters2.Count;
|
|
||||||
for (var i = 0; i < parameterCount1; i++)
|
|
||||||
{
|
|
||||||
var parameter1 = parameters1[i];
|
|
||||||
var found = false;
|
|
||||||
for (var j = 0; j < parameterCount2; j++)
|
|
||||||
{
|
|
||||||
var parameter2 = parameters2[j];
|
|
||||||
if (parameter1.Equals(parameter2))
|
|
||||||
{
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!found)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,116 +0,0 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Net.Http.Headers;
|
|
||||||
|
|
||||||
namespace System.Net.Http.Formatting
|
|
||||||
{
|
|
||||||
/// Implementation of <see cref="IComparer{T}"/> that can compare accept media type header fields
|
|
||||||
/// based on their quality values (a.k.a q-values). See
|
|
||||||
/// <see cref="StringWithQualityHeaderValueComparer"/> for a comparer for other content negotiation
|
|
||||||
/// header field q-values.
|
|
||||||
internal class MediaTypeWithQualityHeaderValueComparer : IComparer<MediaTypeWithQualityHeaderValue>
|
|
||||||
{
|
|
||||||
private static readonly MediaTypeWithQualityHeaderValueComparer _mediaTypeComparer =
|
|
||||||
new MediaTypeWithQualityHeaderValueComparer();
|
|
||||||
|
|
||||||
private MediaTypeWithQualityHeaderValueComparer()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public static MediaTypeWithQualityHeaderValueComparer QualityComparer
|
|
||||||
{
|
|
||||||
get { return _mediaTypeComparer; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Compares two <see cref="MediaTypeWithQualityHeaderValue"/> based on their quality value (a.k.a their
|
|
||||||
/// "q-value"). Values with identical q-values are considered equal (i.e the result is 0) with the exception
|
|
||||||
/// that sub-type wild-cards are considered less than specific media types and full wild-cards are considered
|
|
||||||
/// less than sub-type wild-cards. This allows to sort a sequence of <see cref="StringWithQualityHeaderValue"/>
|
|
||||||
/// following their q-values in the order of specific media types, subtype wild-cards, and last any full
|
|
||||||
/// wild-cards.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="mediaType1">The first <see cref="MediaTypeWithQualityHeaderValue"/> to compare.</param>
|
|
||||||
/// <param name="mediaType2">The second <see cref="MediaTypeWithQualityHeaderValue"/> to compare.</param>
|
|
||||||
/// <returns>
|
|
||||||
/// <c>0</c> if <paramref name="mediaType1"/> and <paramref name="mediaType2"/> are considered equal.
|
|
||||||
/// <c>1</c> if <paramref name="mediaType1"/> is considered greater than <paramref name="mediaType2"/>.
|
|
||||||
/// <c>-1</c> otherwise (<paramref name="mediaType1"/> is considered less than <paramref name="mediaType2"/>).
|
|
||||||
/// </returns>
|
|
||||||
public int Compare(MediaTypeWithQualityHeaderValue mediaType1, MediaTypeWithQualityHeaderValue mediaType2)
|
|
||||||
{
|
|
||||||
Debug.Assert(mediaType1 != null, "The 'mediaType1' parameter should not be null.");
|
|
||||||
Debug.Assert(mediaType2 != null, "The 'mediaType2' parameter should not be null.");
|
|
||||||
|
|
||||||
if (ReferenceEquals(mediaType1, mediaType2))
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
var returnValue = CompareBasedOnQualityFactor(mediaType1, mediaType2);
|
|
||||||
if (returnValue == 0)
|
|
||||||
{
|
|
||||||
var parsedMediaType1 = new ParsedMediaTypeHeaderValue(mediaType1);
|
|
||||||
var parsedMediaType2 = new ParsedMediaTypeHeaderValue(mediaType2);
|
|
||||||
|
|
||||||
if (!parsedMediaType1.TypesEqual(ref parsedMediaType2))
|
|
||||||
{
|
|
||||||
if (parsedMediaType1.IsAllMediaRange)
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
else if (parsedMediaType2.IsAllMediaRange)
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
else if (parsedMediaType1.IsSubtypeMediaRange && !parsedMediaType2.IsSubtypeMediaRange)
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
else if (!parsedMediaType1.IsSubtypeMediaRange && parsedMediaType2.IsSubtypeMediaRange)
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (!parsedMediaType1.SubTypesEqual(ref parsedMediaType2))
|
|
||||||
{
|
|
||||||
if (parsedMediaType1.IsSubtypeMediaRange)
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
else if (parsedMediaType2.IsSubtypeMediaRange)
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return returnValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int CompareBasedOnQualityFactor(
|
|
||||||
MediaTypeWithQualityHeaderValue mediaType1,
|
|
||||||
MediaTypeWithQualityHeaderValue mediaType2)
|
|
||||||
{
|
|
||||||
Debug.Assert(mediaType1 != null);
|
|
||||||
Debug.Assert(mediaType2 != null);
|
|
||||||
|
|
||||||
var mediaType1Quality = mediaType1.Quality ?? FormattingUtilities.Match;
|
|
||||||
var mediaType2Quality = mediaType2.Quality ?? FormattingUtilities.Match;
|
|
||||||
var qualityDifference = mediaType1Quality - mediaType2Quality;
|
|
||||||
if (qualityDifference < 0)
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
else if (qualityDifference > 0)
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,88 +0,0 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Net.Http.Headers;
|
|
||||||
|
|
||||||
namespace System.Net.Http.Formatting
|
|
||||||
{
|
|
||||||
// This type is instantiated by frequently called comparison methods so is very performance sensitive
|
|
||||||
internal struct ParsedMediaTypeHeaderValue
|
|
||||||
{
|
|
||||||
private const char MediaRangeAsterisk = '*';
|
|
||||||
private const char MediaTypeSubtypeDelimiter = '/';
|
|
||||||
|
|
||||||
private readonly string _mediaType;
|
|
||||||
private readonly int _delimiterIndex;
|
|
||||||
private readonly bool _isAllMediaRange;
|
|
||||||
private readonly bool _isSubtypeMediaRange;
|
|
||||||
|
|
||||||
public ParsedMediaTypeHeaderValue(MediaTypeHeaderValue mediaTypeHeaderValue)
|
|
||||||
{
|
|
||||||
Debug.Assert(mediaTypeHeaderValue != null);
|
|
||||||
var mediaType = _mediaType = mediaTypeHeaderValue.MediaType;
|
|
||||||
_delimiterIndex = mediaType.IndexOf(MediaTypeSubtypeDelimiter);
|
|
||||||
Debug.Assert(
|
|
||||||
_delimiterIndex > 0,
|
|
||||||
"The constructor of the MediaTypeHeaderValue would have failed if there wasn't a type and subtype.");
|
|
||||||
|
|
||||||
_isAllMediaRange = false;
|
|
||||||
_isSubtypeMediaRange = false;
|
|
||||||
var mediaTypeLength = mediaType.Length;
|
|
||||||
if (_delimiterIndex == mediaTypeLength - 2)
|
|
||||||
{
|
|
||||||
if (mediaType[mediaTypeLength - 1] == MediaRangeAsterisk)
|
|
||||||
{
|
|
||||||
_isSubtypeMediaRange = true;
|
|
||||||
if (_delimiterIndex == 1 && mediaType[0] == MediaRangeAsterisk)
|
|
||||||
{
|
|
||||||
_isAllMediaRange = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsAllMediaRange
|
|
||||||
{
|
|
||||||
get { return _isAllMediaRange; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsSubtypeMediaRange
|
|
||||||
{
|
|
||||||
get { return _isSubtypeMediaRange; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TypesEqual(ref ParsedMediaTypeHeaderValue other)
|
|
||||||
{
|
|
||||||
if (_delimiterIndex != other._delimiterIndex)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return string.Compare(
|
|
||||||
strA: _mediaType,
|
|
||||||
indexA: 0,
|
|
||||||
strB: other._mediaType,
|
|
||||||
indexB: 0,
|
|
||||||
length: _delimiterIndex,
|
|
||||||
comparisonType: StringComparison.OrdinalIgnoreCase) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool SubTypesEqual(ref ParsedMediaTypeHeaderValue other)
|
|
||||||
{
|
|
||||||
var _subTypeLength = _mediaType.Length - _delimiterIndex - 1;
|
|
||||||
if (_subTypeLength != other._mediaType.Length - other._delimiterIndex - 1)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return string.Compare(
|
|
||||||
strA: _mediaType,
|
|
||||||
indexA: _delimiterIndex + 1,
|
|
||||||
strB: other._mediaType,
|
|
||||||
indexB: other._delimiterIndex + 1,
|
|
||||||
length: _subTypeLength,
|
|
||||||
comparisonType: StringComparison.OrdinalIgnoreCase) == 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,75 +0,0 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Net.Http.Headers;
|
|
||||||
|
|
||||||
namespace System.Net.Http.Formatting
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Implementation of <see cref="IComparer{T}"/> that can compare content negotiation header fields
|
|
||||||
/// based on their quality values (a.k.a q-values). This applies to values used in accept-charset,
|
|
||||||
/// accept-encoding, accept-language and related header fields with similar syntax rules. See
|
|
||||||
/// <see cref="MediaTypeWithQualityHeaderValueComparer"/> for a comparer for media type
|
|
||||||
/// q-values.
|
|
||||||
/// </summary>
|
|
||||||
internal class StringWithQualityHeaderValueComparer : IComparer<StringWithQualityHeaderValue>
|
|
||||||
{
|
|
||||||
private static readonly StringWithQualityHeaderValueComparer _qualityComparer =
|
|
||||||
new StringWithQualityHeaderValueComparer();
|
|
||||||
|
|
||||||
private StringWithQualityHeaderValueComparer()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public static StringWithQualityHeaderValueComparer QualityComparer
|
|
||||||
{
|
|
||||||
get { return _qualityComparer; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Compares two <see cref="StringWithQualityHeaderValue"/> based on their quality value (a.k.a their
|
|
||||||
/// "q-value"). Values with identical q-values are considered equal (i.e the result is 0) with the exception of
|
|
||||||
/// wild-card values (i.e. a value of "*") which are considered less than non-wild-card values. This allows to
|
|
||||||
/// sort a sequence of <see cref="StringWithQualityHeaderValue"/> following their q-values ending up with any
|
|
||||||
/// wild-cards at the end.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="stringWithQuality1">The first value to compare.</param>
|
|
||||||
/// <param name="stringWithQuality2">The second value to compare</param>
|
|
||||||
/// <returns>The result of the comparison.</returns>
|
|
||||||
public int Compare(
|
|
||||||
StringWithQualityHeaderValue stringWithQuality1,
|
|
||||||
StringWithQualityHeaderValue stringWithQuality2)
|
|
||||||
{
|
|
||||||
Debug.Assert(stringWithQuality1 != null);
|
|
||||||
Debug.Assert(stringWithQuality2 != null);
|
|
||||||
|
|
||||||
var quality1 = stringWithQuality1.Quality ?? FormattingUtilities.Match;
|
|
||||||
var quality2 = stringWithQuality2.Quality ?? FormattingUtilities.Match;
|
|
||||||
var qualityDifference = quality1 - quality2;
|
|
||||||
if (qualityDifference < 0)
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
else if (qualityDifference > 0)
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.Equals(stringWithQuality1.Value, stringWithQuality2.Value, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
if (string.Equals(stringWithQuality1.Value, "*", StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
else if (string.Equals(stringWithQuality2.Value, "*", StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -8,9 +8,6 @@ System.Web.Http.ApiController</Description>
|
||||||
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<PackageTags>aspnetcore;aspnetcoremvc;aspnetwebapi</PackageTags>
|
<PackageTags>aspnetcore;aspnetcoremvc;aspnetwebapi</PackageTags>
|
||||||
<!-- Removing this is blocked by https://github.com/aspnet/Mvc/issues/5822 -->
|
|
||||||
<PackageTargetFallback>$(PackageTargetFallback);portable-net451+win8</PackageTargetFallback>
|
|
||||||
<DisableImplicitAssetTargetFallback>true</DisableImplicitAssetTargetFallback>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
[assembly: TypeForwardedTo(typeof(System.Net.Http.Formatting.ContentNegotiationResult))]
|
||||||
|
[assembly: TypeForwardedTo(typeof(System.Net.Http.Formatting.DefaultContentNegotiator))]
|
||||||
|
[assembly: TypeForwardedTo(typeof(System.Net.Http.Formatting.FormDataCollection ))]
|
||||||
|
[assembly: TypeForwardedTo(typeof(System.Net.Http.Formatting.IContentNegotiator))]
|
||||||
|
[assembly: TypeForwardedTo(typeof(System.Net.Http.Formatting.MediaTypeFormatterMatch))]
|
||||||
|
[assembly: TypeForwardedTo(typeof(System.Net.Http.Formatting.MediaTypeFormatterMatchRanking))]
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"TypeId": "public enum System.Net.Http.Formatting.MediaTypeFormatterMatchRanking",
|
||||||
|
"MemberId": "MatchOnRequestMediaType = 5",
|
||||||
|
"Kind": "Removal"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
@ -3,9 +3,6 @@
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
|
<TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
|
||||||
<!-- Removing this is blocked by https://github.com/aspnet/Mvc/issues/5822 -->
|
|
||||||
<PackageTargetFallback Condition=" '$(TargetFramework)' == 'netcoreapp2.1' OR '$(TargetFramework)' == 'netcoreapp2.0' ">$(PackageTargetFallback);portable-net451+win8</PackageTargetFallback>
|
|
||||||
<DisableImplicitAssetTargetFallback>true</DisableImplicitAssetTargetFallback>
|
|
||||||
<PlatformTarget Condition=" '$(TargetFramework)' == 'netcoreapp2.1' "></PlatformTarget>
|
<PlatformTarget Condition=" '$(TargetFramework)' == 'netcoreapp2.1' "></PlatformTarget>
|
||||||
|
|
||||||
<DefineConstants Condition="'$(GenerateBaselines)'=='true'">$(DefineConstants);GENERATE_BASELINES</DefineConstants>
|
<DefineConstants Condition="'$(GenerateBaselines)'=='true'">$(DefineConstants);GENERATE_BASELINES</DefineConstants>
|
||||||
|
|
|
||||||
|
|
@ -62,11 +62,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
{
|
{
|
||||||
typeof(JsonMediaTypeFormatter).FullName,
|
typeof(JsonMediaTypeFormatter).FullName,
|
||||||
typeof(XmlMediaTypeFormatter).FullName,
|
typeof(XmlMediaTypeFormatter).FullName,
|
||||||
|
|
||||||
#if NET461
|
|
||||||
// We call into WebAPI and ask it to add all of its formatters. On net461 it adds this additional formatter.
|
|
||||||
typeof(FormUrlEncodedMediaTypeFormatter).FullName
|
typeof(FormUrlEncodedMediaTypeFormatter).FullName
|
||||||
#endif
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>netcoreapp2.1</TargetFrameworks>
|
<TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
|
||||||
<TargetFrameworks Condition=" '$(DeveloperBuild)' != 'true' ">$(TargetFrameworks);netcoreapp2.0</TargetFrameworks>
|
|
||||||
|
|
||||||
<!-- Removing this is blocked by https://github.com/aspnet/Mvc/issues/5822 -->
|
|
||||||
<PackageTargetFallback Condition=" '$(TargetFramework)' == 'netcoreapp2.1' OR '$(TargetFramework)' == 'netcoreapp2.0' ">$(PackageTargetFallback);portable-net451+win8</PackageTargetFallback>
|
|
||||||
<DisableImplicitAssetTargetFallback>true</DisableImplicitAssetTargetFallback>
|
|
||||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||||
<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
|
<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,6 @@
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>$(StandardTestWebsiteTfms)</TargetFrameworks>
|
<TargetFrameworks>$(StandardTestWebsiteTfms)</TargetFrameworks>
|
||||||
<PackageTargetFallback Condition=" '$(TargetFramework)' == 'netcoreapp2.1' OR '$(TargetFramework)' == 'netcoreapp2.0' ">$(PackageTargetFallback);portable-net451+win8</PackageTargetFallback>
|
|
||||||
<DisableImplicitAssetTargetFallback>true</DisableImplicitAssetTargetFallback>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue