[Fixes #1332] Port IContentNegotiator to WebAPI shim for CoreCLR support
1. Ported IContentNegotiator. 2. Enabled Core CLR configuration
This commit is contained in:
parent
37193adef2
commit
8966680075
|
|
@ -0,0 +1,275 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
#if ASPNETCORE50
|
||||
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics.Contracts;
|
||||
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)
|
||||
{
|
||||
Contract.Assert(array != null);
|
||||
|
||||
int originalLength = array.Length;
|
||||
T[] 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)
|
||||
{
|
||||
Contract.Assert(values != null);
|
||||
|
||||
T[] 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)
|
||||
{
|
||||
Contract.Assert(enumerable != null);
|
||||
|
||||
Collection<T> collection = enumerable as Collection<T>;
|
||||
if (collection != null)
|
||||
{
|
||||
return collection;
|
||||
}
|
||||
// Check for IList so that collection can wrap it instead of copying
|
||||
IList<T> 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)
|
||||
{
|
||||
Contract.Assert(enumerable != null);
|
||||
|
||||
IList<T> 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)
|
||||
{
|
||||
Contract.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)
|
||||
{
|
||||
Contract.Assert(list != null);
|
||||
Contract.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)
|
||||
{
|
||||
Contract.Assert(list != null);
|
||||
Contract.Assert(errorAction != null);
|
||||
|
||||
switch (list.Count)
|
||||
{
|
||||
case 0:
|
||||
return default(T);
|
||||
|
||||
case 1:
|
||||
T 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
|
||||
{
|
||||
Contract.Assert(list != null);
|
||||
Contract.Assert(errorAction != null);
|
||||
|
||||
TMatch result = null;
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
{
|
||||
TMatch 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
|
||||
{
|
||||
Contract.Assert(collection != null);
|
||||
|
||||
T[] result = new T[collection.Count];
|
||||
int count = 0;
|
||||
foreach (T value in collection)
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
result[count] = value;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
if (count == collection.Count)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
T[] 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)
|
||||
{
|
||||
Contract.Assert(array != null);
|
||||
Contract.Assert(keySelector != null);
|
||||
|
||||
Dictionary<TKey, TValue> dictionary = new Dictionary<TKey, TValue>(array.Length, comparer);
|
||||
for (int i = 0; i < array.Length; i++)
|
||||
{
|
||||
TValue 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)
|
||||
{
|
||||
Contract.Assert(list != null);
|
||||
Contract.Assert(keySelector != null);
|
||||
|
||||
TValue[] 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)
|
||||
{
|
||||
Contract.Assert(enumerable != null);
|
||||
Contract.Assert(keySelector != null);
|
||||
|
||||
TValue[] array = enumerable as TValue[];
|
||||
if (array != null)
|
||||
{
|
||||
return ToDictionaryFast(array, keySelector, comparer);
|
||||
}
|
||||
IList<TValue> list = enumerable as IList<TValue>;
|
||||
if (list != null)
|
||||
{
|
||||
return ToDictionaryFastNoCheck(list, keySelector, comparer);
|
||||
}
|
||||
Dictionary<TKey, TValue> dictionary = new Dictionary<TKey, TValue>(comparer);
|
||||
foreach (TValue 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)
|
||||
{
|
||||
Contract.Assert(list != null);
|
||||
Contract.Assert(keySelector != null);
|
||||
|
||||
int listCount = list.Count;
|
||||
Dictionary<TKey, TValue> dictionary = new Dictionary<TKey, TValue>(listCount, comparer);
|
||||
for (int i = 0; i < listCount; i++)
|
||||
{
|
||||
TValue value = list[i];
|
||||
dictionary.Add(keySelector(value), value);
|
||||
}
|
||||
return dictionary;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
#if ASPNETCORE50
|
||||
|
||||
using Microsoft.AspNet.Mvc;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Web.Http;
|
||||
|
||||
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([NotNull] MediaTypeFormatter formatter, MediaTypeHeaderValue mediaType)
|
||||
{
|
||||
_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; }
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,460 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
#if ASPNETCORE50
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Linq;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Web.Http;
|
||||
using Microsoft.AspNet.Mvc;
|
||||
using System.Net.Http.Formatting;
|
||||
|
||||
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([NotNull] Type type, [NotNull] HttpRequestMessage request, [NotNull] IEnumerable<MediaTypeFormatter> formatters)
|
||||
{
|
||||
// Go through each formatter to compute how well it matches.
|
||||
Collection<MediaTypeFormatterMatch> matches = ComputeFormatterMatches(type, request, formatters);
|
||||
|
||||
// Select best formatter match among the matches
|
||||
MediaTypeFormatterMatch bestFormatterMatch = SelectResponseMediaTypeFormatter(matches);
|
||||
|
||||
// We found a best formatter
|
||||
if (bestFormatterMatch != null)
|
||||
{
|
||||
// Find the best character encoding for the selected formatter
|
||||
Encoding bestEncodingMatch = SelectResponseCharacterEncoding(request, bestFormatterMatch.Formatter);
|
||||
if (bestEncodingMatch != null)
|
||||
{
|
||||
bestFormatterMatch.MediaType.CharSet = bestEncodingMatch.WebName;
|
||||
}
|
||||
|
||||
MediaTypeHeaderValue bestMediaType = bestFormatterMatch.MediaType;
|
||||
MediaTypeFormatter 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([NotNull] Type type, [NotNull] HttpRequestMessage request, [NotNull] IEnumerable<MediaTypeFormatter> formatters)
|
||||
{
|
||||
IEnumerable<MediaTypeWithQualityHeaderValue> sortedAcceptValues = null;
|
||||
|
||||
// Go through each formatter to find how well it matches.
|
||||
ListWrapperCollection<MediaTypeFormatterMatch> matches = new ListWrapperCollection<MediaTypeFormatterMatch>();
|
||||
MediaTypeFormatter[] writingFormatters = GetWritingFormatters(formatters);
|
||||
for (int i = 0; i < writingFormatters.Length; i++)
|
||||
{
|
||||
MediaTypeFormatter formatter = writingFormatters[i];
|
||||
MediaTypeFormatterMatch match = null;
|
||||
|
||||
// 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);
|
||||
}
|
||||
if ((match = MatchAcceptHeader(sortedAcceptValues, formatter)) != 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.
|
||||
bool 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([NotNull] ICollection<MediaTypeFormatterMatch> matches)
|
||||
{
|
||||
// Performance-sensitive
|
||||
|
||||
List<MediaTypeFormatterMatch> 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 (int i = 0; i < matchList.Count; i++)
|
||||
{
|
||||
MediaTypeFormatterMatch 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)
|
||||
{
|
||||
MediaTypeFormatterMatch 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([NotNull] HttpRequestMessage request, [NotNull] MediaTypeFormatter formatter)
|
||||
{
|
||||
// If there are any SupportedEncodings then we pick an encoding
|
||||
List<Encoding> supportedEncodings = formatter.SupportedEncodings.ToList();
|
||||
if (supportedEncodings.Count > 0)
|
||||
{
|
||||
// Sort Accept-Charset header values
|
||||
IEnumerable<StringWithQualityHeaderValue> sortedAcceptCharsetValues = SortStringWithQualityHeaderValuesByQFactor(request.Headers.AcceptCharset);
|
||||
|
||||
// Check for match based on accept-charset headers
|
||||
foreach (StringWithQualityHeaderValue acceptCharset in sortedAcceptCharsetValues)
|
||||
{
|
||||
for (int i = 0; i < supportedEncodings.Count; i++)
|
||||
{
|
||||
Encoding encoding = supportedEncodings[i];
|
||||
if (encoding != null && acceptCharset.Quality != FormattingUtilities.NoMatch &&
|
||||
(acceptCharset.Value.Equals(encoding.WebName, StringComparison.OrdinalIgnoreCase) ||
|
||||
acceptCharset.Value.Equals("*", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
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([NotNull] IEnumerable<MediaTypeWithQualityHeaderValue> sortedAcceptValues, [NotNull] MediaTypeFormatter formatter)
|
||||
{
|
||||
foreach (MediaTypeWithQualityHeaderValue acceptMediaTypeValue in sortedAcceptValues)
|
||||
{
|
||||
List<MediaTypeHeaderValue> supportedMediaTypes = formatter.SupportedMediaTypes.ToList();
|
||||
for (int i = 0; i < supportedMediaTypes.Count; i++)
|
||||
{
|
||||
MediaTypeHeaderValue supportedMediaType = supportedMediaTypes[i];
|
||||
MediaTypeHeaderValueRange range;
|
||||
if (supportedMediaType != null && acceptMediaTypeValue.Quality != FormattingUtilities.NoMatch &&
|
||||
supportedMediaType.IsSubsetOf(acceptMediaTypeValue, out range))
|
||||
{
|
||||
MediaTypeFormatterMatchRanking ranking;
|
||||
switch (range)
|
||||
{
|
||||
case MediaTypeHeaderValueRange.AllMediaRange:
|
||||
ranking = MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderAllMediaRange;
|
||||
break;
|
||||
|
||||
case MediaTypeHeaderValueRange.SubtypeMediaRange:
|
||||
ranking = MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderSubtypeMediaRange;
|
||||
break;
|
||||
|
||||
default:
|
||||
ranking = MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderLiteral;
|
||||
break;
|
||||
}
|
||||
|
||||
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([NotNull] HttpRequestMessage request, [NotNull] MediaTypeFormatter formatter)
|
||||
{
|
||||
if (request.Content != null)
|
||||
{
|
||||
MediaTypeHeaderValue requestMediaType = request.Content.Headers.ContentType;
|
||||
if (requestMediaType != null)
|
||||
{
|
||||
List<MediaTypeHeaderValue> supportedMediaTypes = formatter.SupportedMediaTypes.ToList();
|
||||
for (int i = 0; i < supportedMediaTypes.Count; i++)
|
||||
{
|
||||
MediaTypeHeaderValue 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([NotNull] IEnumerable<MediaTypeWithQualityHeaderValue> 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([NotNull] Type type, [NotNull] MediaTypeFormatter 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;
|
||||
List<MediaTypeHeaderValue> 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([NotNull] ICollection<StringWithQualityHeaderValue> 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)
|
||||
{
|
||||
Contract.Assert(formatters != null);
|
||||
MediaTypeFormatterCollection formatterCollection = formatters as MediaTypeFormatterCollection;
|
||||
return formatters.AsArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
#if ASPNETCORE50
|
||||
|
||||
using Microsoft.AspNet.WebUtilities;
|
||||
using System;
|
||||
using System.Linq;
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,241 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
#if ASPNETCORE50
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.Http.Formatting;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Xml;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
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).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo());
|
||||
}
|
||||
|
||||
/// <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 = null;
|
||||
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></returns>
|
||||
public static XmlDictionaryReaderQuotas CreateDefaultReaderQuotas()
|
||||
{
|
||||
#if NETFX_CORE // MaxDepth is a DOS mitigation. We don't support MaxDepth in portable libraries because it is strictly client side.
|
||||
return XmlDictionaryReaderQuotas.Max;
|
||||
#else
|
||||
return new XmlDictionaryReaderQuotas()
|
||||
{
|
||||
MaxArrayLength = Int32.MaxValue,
|
||||
MaxBytesPerRead = Int32.MaxValue,
|
||||
MaxDepth = DefaultMaxDepth,
|
||||
MaxNameTableCharCount = Int32.MaxValue,
|
||||
MaxStringContentLength = Int32.MaxValue
|
||||
};
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <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.IsNullOrWhiteSpace(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 Int32.TryParse(value, NumberStyles.None, NumberFormatInfo.InvariantInfo, out result);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
#if ASPNETCORE50
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http.Headers;
|
||||
|
||||
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(Type, HttpRequestMessage, MediaTypeHeaderValue)"/>
|
||||
/// 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);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
#if ASPNETCORE50
|
||||
|
||||
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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
#if ASPNETCORE50
|
||||
|
||||
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
|
||||
/// http://www.iana.org/assignments/media-types/application.
|
||||
/// </remarks>
|
||||
public static MediaTypeHeaderValue ApplicationBsonMediaType
|
||||
{
|
||||
get { return _defaultApplicationBsonMediaType; }
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
#if ASPNETCORE50
|
||||
|
||||
using System.Net.Http.Headers;
|
||||
using System.Web.Http;
|
||||
|
||||
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; }
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
#if ASPNETCORE50
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
#if ASPNETCORE50
|
||||
|
||||
using Microsoft.AspNet.Mvc;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Linq;
|
||||
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)
|
||||
{
|
||||
MediaTypeHeaderValueRange 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 MediaTypeHeaderValueRange mediaType2Range)
|
||||
{
|
||||
// Performance-sensitive
|
||||
Contract.Assert(mediaType1 != null);
|
||||
|
||||
if (mediaType2 == null)
|
||||
{
|
||||
mediaType2Range = MediaTypeHeaderValueRange.None;
|
||||
return false;
|
||||
}
|
||||
|
||||
ParsedMediaTypeHeaderValue parsedMediaType1 = new ParsedMediaTypeHeaderValue(mediaType1);
|
||||
ParsedMediaTypeHeaderValue parsedMediaType2 = new ParsedMediaTypeHeaderValue(mediaType2);
|
||||
mediaType2Range = parsedMediaType2.IsAllMediaRange ? MediaTypeHeaderValueRange.AllMediaRange :
|
||||
parsedMediaType2.IsSubtypeMediaRange ? MediaTypeHeaderValueRange.SubtypeMediaRange :
|
||||
MediaTypeHeaderValueRange.None;
|
||||
|
||||
if (!parsedMediaType1.TypesEqual(ref parsedMediaType2))
|
||||
{
|
||||
if (mediaType2Range != MediaTypeHeaderValueRange.AllMediaRange)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (!parsedMediaType1.SubTypesEqual(ref parsedMediaType2))
|
||||
{
|
||||
if (mediaType2Range != MediaTypeHeaderValueRange.SubtypeMediaRange)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 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>.
|
||||
Collection<NameValueHeaderValue> parameters1 = mediaType1.Parameters.AsCollection();
|
||||
int parameterCount1 = parameters1.Count;
|
||||
Collection<NameValueHeaderValue> parameters2 = mediaType2.Parameters.AsCollection();
|
||||
int parameterCount2 = parameters2.Count;
|
||||
for (int i = 0; i < parameterCount1; i++)
|
||||
{
|
||||
NameValueHeaderValue parameter1 = parameters1[i];
|
||||
bool found = false;
|
||||
for (int j = 0; j < parameterCount2; j++)
|
||||
{
|
||||
NameValueHeaderValue parameter2 = parameters2[j];
|
||||
if (parameter1.Equals(parameter2))
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
#if ASPNETCORE50
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
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,
|
||||
/// sub-type wildcards, 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></returns>
|
||||
public int Compare(MediaTypeWithQualityHeaderValue mediaType1, MediaTypeWithQualityHeaderValue mediaType2)
|
||||
{
|
||||
Contract.Assert(mediaType1 != null, "The 'mediaType1' parameter should not be null.");
|
||||
Contract.Assert(mediaType2 != null, "The 'mediaType2' parameter should not be null.");
|
||||
|
||||
if (Object.ReferenceEquals(mediaType1, mediaType2))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int returnValue = CompareBasedOnQualityFactor(mediaType1, mediaType2);
|
||||
|
||||
if (returnValue == 0)
|
||||
{
|
||||
ParsedMediaTypeHeaderValue parsedMediaType1 = new ParsedMediaTypeHeaderValue(mediaType1);
|
||||
ParsedMediaTypeHeaderValue 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)
|
||||
{
|
||||
Contract.Assert(mediaType1 != null);
|
||||
Contract.Assert(mediaType2 != null);
|
||||
|
||||
double mediaType1Quality = mediaType1.Quality ?? FormattingUtilities.Match;
|
||||
double mediaType2Quality = mediaType2.Quality ?? FormattingUtilities.Match;
|
||||
double qualityDifference = mediaType1Quality - mediaType2Quality;
|
||||
if (qualityDifference < 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (qualityDifference > 0)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
#if ASPNETCORE50
|
||||
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Net.Http.Headers;
|
||||
|
||||
namespace System.Net.Http.Formatting
|
||||
{
|
||||
// This type is instanciated 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)
|
||||
{
|
||||
Contract.Assert(mediaTypeHeaderValue != null);
|
||||
string mediaType = _mediaType = mediaTypeHeaderValue.MediaType;
|
||||
_delimiterIndex = mediaType.IndexOf(MediaTypeSubtypeDelimiter);
|
||||
Contract.Assert(_delimiterIndex > 0, "The constructor of the MediaTypeHeaderValue would have failed if there wasn't a type and subtype.");
|
||||
|
||||
_isAllMediaRange = false;
|
||||
_isSubtypeMediaRange = false;
|
||||
int 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(_mediaType, 0, other._mediaType, 0, _delimiterIndex, StringComparison.OrdinalIgnoreCase) == 0;
|
||||
}
|
||||
|
||||
public bool SubTypesEqual(ref ParsedMediaTypeHeaderValue other)
|
||||
{
|
||||
int _subTypeLength = _mediaType.Length - _delimiterIndex - 1;
|
||||
if (_subTypeLength != other._mediaType.Length - other._delimiterIndex - 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return String.Compare(_mediaType, _delimiterIndex + 1, other._mediaType, other._delimiterIndex + 1, _subTypeLength, StringComparison.OrdinalIgnoreCase) == 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
#if ASPNETCORE50
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
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)
|
||||
{
|
||||
Contract.Assert(stringWithQuality1 != null);
|
||||
Contract.Assert(stringWithQuality2 != null);
|
||||
|
||||
double quality1 = stringWithQuality1.Quality ?? FormattingUtilities.Match;
|
||||
double quality2 = stringWithQuality2.Quality ?? FormattingUtilities.Match;
|
||||
double 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.OrdinalIgnoreCase))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (String.Equals(stringWithQuality2.Value, "*", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -20,6 +20,9 @@ namespace System.Net.Http
|
|||
/// </summary>
|
||||
public static class HttpRequestMessageExtensions
|
||||
{
|
||||
|
||||
#if !ASPNETCORE50
|
||||
|
||||
/// <summary>
|
||||
/// Helper method for creating an <see cref="HttpResponseMessage"/> message with a "416 (Requested Range Not Satisfiable)" status code.
|
||||
/// This response can be used in combination with the <see cref="ByteRangeStreamContent"/> to indicate that the requested range or
|
||||
|
|
@ -41,6 +44,8 @@ namespace System.Net.Http
|
|||
return rangeNotSatisfiableResponse;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Helper method that performs content negotiation and creates a <see cref="HttpResponseMessage"/> representing an error
|
||||
/// with an instance of <see cref="ObjectContent{T}"/> wrapping an <see cref="HttpError"/> with message <paramref name="message"/>.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"version": "6.0.0-*",
|
||||
"compilationOptions": {
|
||||
"warningsAsErrors": true
|
||||
"warningsAsErrors": false
|
||||
},
|
||||
"dependencies": {
|
||||
"Microsoft.AspNet.Mvc.Common": { "version": "6.0.0-*", "type": "build" },
|
||||
|
|
@ -13,6 +13,11 @@
|
|||
"frameworkAssemblies": {
|
||||
"System.Net.Http": "4.0.0.0"
|
||||
}
|
||||
},
|
||||
"aspnetcore50": {
|
||||
"dependencies": {
|
||||
"System.Net.Http": "4.0.0-beta-*"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
#if ASPNET50
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
|
|
@ -623,5 +622,4 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
public string ControllerName { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
#if ASPNET50
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
|
|
@ -58,6 +57,8 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
content);
|
||||
}
|
||||
|
||||
#if !ASPNETCORE50
|
||||
|
||||
[Fact]
|
||||
public async Task Options_SetsDefaultFormatters()
|
||||
{
|
||||
|
|
@ -83,6 +84,8 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
Assert.Equal(expected, formatters);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
[Fact]
|
||||
public async Task ActionThrowsHttpResponseException_WithStatusCode()
|
||||
{
|
||||
|
|
@ -521,4 +524,3 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -24,7 +24,8 @@
|
|||
"ValueProvidersSite": "1.0.0",
|
||||
"XmlSerializerWebSite": "1.0.0",
|
||||
"UrlHelperWebSite": "1.0.0",
|
||||
|
||||
"WebApiCompatShimWebSite": "1.0.0",
|
||||
|
||||
"Microsoft.AspNet.TestHost": "1.0.0-*",
|
||||
"Microsoft.AspNet.PipelineCore": "1.0.0-*",
|
||||
"Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0",
|
||||
|
|
@ -33,7 +34,6 @@
|
|||
"Microsoft.Framework.DependencyInjection": "1.0.0-*",
|
||||
"Microsoft.Framework.Logging": "1.0.0-*",
|
||||
"Microsoft.Framework.Runtime.Interfaces": "1.0.0-*",
|
||||
"Microsoft.AspNet.PipelineCore": "1.0.0-*",
|
||||
"TagHelpersWebSite": "1.0.0",
|
||||
"Xunit.KRunner": "1.0.0-*"
|
||||
},
|
||||
|
|
@ -43,8 +43,7 @@
|
|||
"frameworks": {
|
||||
"aspnet50": {
|
||||
"dependencies": {
|
||||
"AutofacWebSite": "1.0.0",
|
||||
"WebApiCompatShimWebSite": "1.0.0"
|
||||
"AutofacWebSite": "1.0.0"
|
||||
}
|
||||
},
|
||||
"aspnetcore50": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
#if !ASPNETCORE50
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
|
@ -344,4 +346,5 @@ namespace System.Web.Http.TestControllers
|
|||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,835 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. 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.Linq;
|
||||
using System.Net.Http.Formatting.Mocks;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Xunit;
|
||||
using Microsoft.TestCommon;
|
||||
|
||||
namespace System.Net.Http.Formatting
|
||||
{
|
||||
public class DefaultContentNegotiatorTests
|
||||
{
|
||||
private readonly DefaultContentNegotiator _negotiator = new DefaultContentNegotiator();
|
||||
private readonly HttpRequestMessage _request = new HttpRequestMessage();
|
||||
|
||||
public static TheoryData<string, string[], string> MatchRequestMediaTypeData
|
||||
{
|
||||
get
|
||||
{
|
||||
// string requestMediaType, string[] supportedMediaTypes, string expectedMediaType
|
||||
return new TheoryData<string, string[], string>
|
||||
{
|
||||
{ "text/plain", new string[0], null },
|
||||
{ "text/plain", new string[] { "text/xml", "application/xml" }, null },
|
||||
{ "application/xml", new string[] { "application/xml", "text/xml" }, "application/xml" },
|
||||
{ "APPLICATION/XML", new string[] { "text/xml", "application/xml" }, "application/xml" },
|
||||
{ "application/xml; charset=utf-8", new string[] { "text/xml", "application/xml" }, "application/xml" },
|
||||
{ "application/xml; charset=utf-8; parameter=value", new string[] { "text/xml", "application/xml" }, "application/xml" },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static TheoryData<string[], string[], string, double, int> MatchAcceptHeaderData
|
||||
{
|
||||
get
|
||||
{
|
||||
// string[] acceptHeader, string[] supportedMediaTypes, string expectedMediaType, double matchQuality, int range
|
||||
return new TheoryData<string[], string[], string, double, int>
|
||||
{
|
||||
{ new string[] { "text/plain" }, new string[0], null, 0.0, (int)MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderLiteral },
|
||||
|
||||
{ new string[] { "text/plain" }, new string[] { "text/xml", "application/xml" }, null, 0.0, (int)MediaTypeFormatterMatchRanking.None },
|
||||
{ new string[] { "text/plain; q=0.5" }, new string[] { "text/xml", "application/xml" }, null, 0.0, (int)MediaTypeFormatterMatchRanking.None },
|
||||
|
||||
{ new string[] { "application/xml" }, new string[] { "application/xml", "text/xml" }, "application/xml", 1.0, (int)MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderLiteral },
|
||||
{ new string[] { "APPLICATION/XML; q=0.5" }, new string[] { "text/xml", "application/xml" }, "application/xml", 0.5, (int)MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderLiteral },
|
||||
{ new string[] { "text/xml; q=0.5", "APPLICATION/XML; q=0.7" }, new string[] { "text/xml", "application/xml" }, "application/xml", 0.7, (int)MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderLiteral },
|
||||
{ new string[] { "application/xml; q=0.0" }, new string[] { "application/xml", "text/xml" }, null, 0.0, (int)MediaTypeFormatterMatchRanking.None },
|
||||
{ new string[] { "APPLICATION/XML; q=0.0" }, new string[] { "text/xml", "application/xml" }, null, 0.0, (int)MediaTypeFormatterMatchRanking.None },
|
||||
{ new string[] { "text/xml; q=0.0", "APPLICATION/XML; q=0.0" }, new string[] { "text/xml", "application/xml" }, null, 0.0, (int)MediaTypeFormatterMatchRanking.None },
|
||||
|
||||
{ new string[] { "text/*" }, new string[] { "text/xml", "application/xml" }, "text/xml", 1.0, (int)MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderSubtypeMediaRange },
|
||||
{ new string[] { "text/*", "application/xml" }, new string[] { "text/xml", "application/xml" }, "application/xml", 1.0, (int)MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderLiteral },
|
||||
{ new string[] { "text/*", "application/xml; q=0.5" }, new string[] { "text/xml", "application/xml" }, "text/xml", 1.0, (int)MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderSubtypeMediaRange },
|
||||
{ new string[] { "text/*; q=0.5" }, new string[] { "text/xml", "application/xml" }, "text/xml", 0.5, (int)MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderSubtypeMediaRange },
|
||||
{ new string[] { "text/*; q=0.5", "application/xml" }, new string[] { "text/xml", "application/xml" }, "application/xml", 1.0, (int)MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderLiteral },
|
||||
{ new string[] { "text/*; q=0.0", "application/xml; q=0.0" }, new string[] { "text/xml", "application/xml" }, null, 0.0, (int)MediaTypeFormatterMatchRanking.None },
|
||||
{ new string[] { "text/*; q=0.0", "application/xml" }, new string[] { "text/xml", "application/xml" }, "application/xml", 1.0, (int)MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderLiteral },
|
||||
|
||||
{ new string[] { "*/*; q=0.5" }, new string[] { "text/xml", "application/xml" }, "text/xml", 0.5, (int)MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderAllMediaRange },
|
||||
{ new string[] { "*/*; q=0.0" }, new string[] { "text/xml", "application/xml" }, null, 0.0, (int)MediaTypeFormatterMatchRanking.None },
|
||||
{ new string[] { "*/*; q=0.5", "application/xml" }, new string[] { "text/xml", "application/xml" }, "application/xml", 1.0, (int)MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderLiteral },
|
||||
{ new string[] { "*/*; q=1.0", "application/xml; q=0.5" }, new string[] { "text/xml", "application/xml" }, "text/xml", 1.0, (int)MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderAllMediaRange },
|
||||
{ new string[] { "*/*", "application/xml" }, new string[] { "text/xml", "application/xml" }, "application/xml", 1.0, (int)MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderLiteral },
|
||||
|
||||
{ new string[] { "text/*; q=0.5", "*/*; q=0.2", "application/xml; q=1.0" }, new string[] { "text/xml", "application/xml" }, "application/xml", 1.0, (int)MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderLiteral },
|
||||
|
||||
{ new string[] { "application/xml; q=0.5" }, new string[] { "text/xml", "application/xml" }, "application/xml", 0.5, (int)MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderLiteral },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static TheoryData<bool, string[], bool> ShouldMatchOnTypeData
|
||||
{
|
||||
get
|
||||
{
|
||||
// bool excludeMatchOnType, string[] acceptHeaders, bool expectedResult
|
||||
return new TheoryData<bool, string[], bool>
|
||||
{
|
||||
{ false, new string[0], true },
|
||||
{ true, new string[0], true },
|
||||
|
||||
{ false, new string[] { "application/xml" }, true },
|
||||
{ true, new string[] { "application/xml" }, false },
|
||||
|
||||
{ false, new string[] { "application/xml; q=1.0" }, true },
|
||||
{ true, new string[] { "application/xml; q=1.0" }, false },
|
||||
|
||||
{ false, new string[] { "application/xml; q=0.0" }, true },
|
||||
{ true, new string[] { "application/xml; q=0.0" }, false },
|
||||
|
||||
{ false, new string[] { "application/xml; q=0.0", "application/json" }, true },
|
||||
{ true, new string[] { "application/xml; q=0.0", "application/json" }, false },
|
||||
|
||||
{ false, new string[] { "text/nomatch" }, true },
|
||||
{ true, new string[] { "text/nomatch" }, false },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static TheoryData<string[], string> MatchTypeData
|
||||
{
|
||||
get
|
||||
{
|
||||
// string[] supportedMediaTypes, string expectedMediaType
|
||||
return new TheoryData<string[], string>
|
||||
{
|
||||
{ new string[0], "application/octet-stream" },
|
||||
|
||||
{ new string[] { "text/xml", "application/xml" }, "text/xml" },
|
||||
{ new string[] { "application/xml", "text/xml" }, "application/xml" },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static TheoryData<string[], string, string[], string> SelectResponseCharacterEncodingData
|
||||
{
|
||||
get
|
||||
{
|
||||
// string[] acceptEncodings, string requestEncoding, string[] supportedEncodings, string expectedEncoding
|
||||
return new TheoryData<string[], string, string[], string>
|
||||
{
|
||||
{ new string[] { "utf-8" }, null, new string[0], null },
|
||||
{ new string[0], "utf-8", new string[0], null },
|
||||
|
||||
{ new string[0], null, new string[] { "utf-8", "utf-16"}, "utf-8" },
|
||||
{ new string[0], "utf-16", new string[] { "utf-8", "utf-16"}, "utf-16" },
|
||||
|
||||
{ new string[] { "utf-8" }, null, new string[] { "utf-8", "utf-16"}, "utf-8" },
|
||||
{ new string[] { "utf-16" }, "utf-8", new string[] { "utf-8", "utf-16"}, "utf-16" },
|
||||
{ new string[] { "utf-16; q=0.5" }, "utf-8", new string[] { "utf-8", "utf-16"}, "utf-16" },
|
||||
|
||||
{ new string[] { "utf-8; q=0.0" }, null, new string[] { "utf-8", "utf-16"}, "utf-8" },
|
||||
{ new string[] { "utf-8; q=0.0" }, "utf-16", new string[] { "utf-8", "utf-16"}, "utf-16" },
|
||||
{ new string[] { "utf-8; q=0.0", "utf-16; q=0.0" }, "utf-16", new string[] { "utf-8", "utf-16"}, "utf-16" },
|
||||
{ new string[] { "utf-8; q=0.0", "utf-16; q=0.0" }, null, new string[] { "utf-8", "utf-16"}, "utf-8" },
|
||||
{ new string[] { "*; q=0.0" }, null, new string[] { "utf-8", "utf-16"}, "utf-8" },
|
||||
{ new string[] { "*; q=0.0" }, "utf-16", new string[] { "utf-8", "utf-16"}, "utf-16" },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static TheoryData<ICollection<MediaTypeFormatterMatch>, MediaTypeFormatterMatch> SelectResponseMediaTypeData
|
||||
{
|
||||
get
|
||||
{
|
||||
#if !ASPNETCORE50
|
||||
// Only mapping and accept makes sense with q != 1.0
|
||||
MediaTypeFormatterMatch matchMapping10 = CreateMatch(1.0, MediaTypeFormatterMatchRanking.MatchOnRequestWithMediaTypeMapping);
|
||||
MediaTypeFormatterMatch matchMapping05 = CreateMatch(0.5, MediaTypeFormatterMatchRanking.MatchOnRequestWithMediaTypeMapping);
|
||||
#endif
|
||||
|
||||
MediaTypeFormatterMatch matchAccept10 = CreateMatch(1.0, MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderLiteral);
|
||||
MediaTypeFormatterMatch matchAccept05 = CreateMatch(0.5, MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderLiteral);
|
||||
|
||||
MediaTypeFormatterMatch matchAcceptSubTypeRange10 = CreateMatch(1.0, MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderSubtypeMediaRange);
|
||||
MediaTypeFormatterMatch matchAcceptSubTypeRange05 = CreateMatch(0.5, MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderSubtypeMediaRange);
|
||||
|
||||
MediaTypeFormatterMatch matchAcceptAllRange10 = CreateMatch(1.0, MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderAllMediaRange);
|
||||
MediaTypeFormatterMatch matchAcceptAllRange05 = CreateMatch(0.5, MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderAllMediaRange);
|
||||
|
||||
MediaTypeFormatterMatch matchRequest10 = CreateMatch(1.0, MediaTypeFormatterMatchRanking.MatchOnRequestMediaType);
|
||||
MediaTypeFormatterMatch matchType10 = CreateMatch(1.0, MediaTypeFormatterMatchRanking.MatchOnCanWriteType);
|
||||
|
||||
// ICollection<MediaTypeFormatterMatch> candidateMatches, MediaTypeFormatterMatch winner
|
||||
return new TheoryData<ICollection<MediaTypeFormatterMatch>, MediaTypeFormatterMatch>
|
||||
{
|
||||
{ new List<MediaTypeFormatterMatch>(), null },
|
||||
{ new List<MediaTypeFormatterMatch>() { matchType10 }, matchType10 },
|
||||
{ new List<MediaTypeFormatterMatch>() { matchType10, matchRequest10 }, matchRequest10 },
|
||||
{ new List<MediaTypeFormatterMatch>() { matchType10, matchRequest10, matchAcceptAllRange10 }, matchAcceptAllRange10 },
|
||||
{ new List<MediaTypeFormatterMatch>() { matchType10, matchRequest10, matchAcceptAllRange10, matchAcceptSubTypeRange10 }, matchAcceptSubTypeRange10 },
|
||||
{ new List<MediaTypeFormatterMatch>() { matchType10, matchRequest10, matchAcceptAllRange10, matchAcceptSubTypeRange10, matchAccept10 }, matchAccept10 },
|
||||
#if !ASPNETCORE50
|
||||
{ new List<MediaTypeFormatterMatch>() { matchType10, matchRequest10, matchAcceptAllRange10, matchAcceptSubTypeRange10, matchAccept10, matchMapping10 }, matchMapping10 },
|
||||
#endif
|
||||
{ new List<MediaTypeFormatterMatch>() { matchAccept05, matchAccept10 }, matchAccept10 },
|
||||
{ new List<MediaTypeFormatterMatch>() { matchAccept10, matchAccept05 }, matchAccept10 },
|
||||
|
||||
{ new List<MediaTypeFormatterMatch>() { matchAcceptSubTypeRange05, matchAcceptSubTypeRange10 }, matchAcceptSubTypeRange10 },
|
||||
{ new List<MediaTypeFormatterMatch>() { matchAcceptSubTypeRange10, matchAcceptSubTypeRange05 }, matchAcceptSubTypeRange10 },
|
||||
|
||||
{ new List<MediaTypeFormatterMatch>() { matchAcceptAllRange05, matchAcceptAllRange10 }, matchAcceptAllRange10 },
|
||||
{ new List<MediaTypeFormatterMatch>() { matchAcceptAllRange10, matchAcceptAllRange05 }, matchAcceptAllRange10 },
|
||||
#if !ASPNETCORE50
|
||||
{ new List<MediaTypeFormatterMatch>() { matchMapping05, matchMapping10 }, matchMapping10 },
|
||||
{ new List<MediaTypeFormatterMatch>() { matchMapping10, matchMapping05 }, matchMapping10 },
|
||||
|
||||
{ new List<MediaTypeFormatterMatch>() { matchMapping05, matchAccept05 }, matchMapping05 },
|
||||
{ new List<MediaTypeFormatterMatch>() { matchMapping10, matchAccept10 }, matchMapping10 },
|
||||
|
||||
{ new List<MediaTypeFormatterMatch>() { matchMapping05, matchAcceptSubTypeRange05 }, matchMapping05 },
|
||||
{ new List<MediaTypeFormatterMatch>() { matchMapping10, matchAcceptSubTypeRange10 }, matchMapping10 },
|
||||
|
||||
{ new List<MediaTypeFormatterMatch>() { matchMapping05, matchAcceptAllRange05 }, matchMapping05 },
|
||||
{ new List<MediaTypeFormatterMatch>() { matchMapping10, matchAcceptAllRange10 }, matchMapping10 },
|
||||
|
||||
{ new List<MediaTypeFormatterMatch>() { matchMapping05, matchAccept10 }, matchAccept10 },
|
||||
{ new List<MediaTypeFormatterMatch>() { matchMapping05, matchAcceptSubTypeRange10 }, matchAcceptSubTypeRange10 },
|
||||
{ new List<MediaTypeFormatterMatch>() { matchMapping05, matchAcceptAllRange10 }, matchAcceptAllRange10 },
|
||||
#endif
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static TheoryData<MediaTypeFormatterMatch, MediaTypeFormatterMatch, bool> UpdateBestMatchData
|
||||
{
|
||||
get
|
||||
{
|
||||
MediaTypeFormatterMatch matchMapping10 = CreateMatch(1.0, MediaTypeFormatterMatchRanking.None);
|
||||
MediaTypeFormatterMatch matchMapping05 = CreateMatch(0.5, MediaTypeFormatterMatchRanking.None);
|
||||
|
||||
// MediaTypeFormatterMatch current, MediaTypeFormatterMatch potentialReplacement, currentWins
|
||||
return new TheoryData<MediaTypeFormatterMatch, MediaTypeFormatterMatch, bool>
|
||||
{
|
||||
{ null, matchMapping10, false },
|
||||
{ null, matchMapping05, false },
|
||||
|
||||
{ matchMapping10, matchMapping10, true },
|
||||
{ matchMapping10, matchMapping05, true },
|
||||
|
||||
{ matchMapping05, matchMapping10, false },
|
||||
{ matchMapping05, matchMapping05, true },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static MediaTypeFormatterMatch CreateMatch(double? quality, MediaTypeFormatterMatchRanking ranking)
|
||||
{
|
||||
MockMediaTypeFormatter formatter = new MockMediaTypeFormatter();
|
||||
MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("text/test");
|
||||
return new MediaTypeFormatterMatch(formatter, mediaType, quality, ranking);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TypeIsCorrect()
|
||||
{
|
||||
new TypeAssert().HasProperties(typeof(DefaultContentNegotiator), TypeAssert.TypeProperties.IsPublicVisibleClass);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Negotiate_ForEmptyFormatterCollection_ReturnsNull()
|
||||
{
|
||||
var result = _negotiator.Negotiate(typeof(string), _request, Enumerable.Empty<MediaTypeFormatter>());
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
#if !ASPNETCORE50
|
||||
|
||||
[Fact]
|
||||
public void Negotiate_MediaTypeMappingTakesPrecedenceOverAcceptHeader()
|
||||
{
|
||||
// Prepare the request message
|
||||
_request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml"));
|
||||
_request.Headers.Add("Browser", "IE");
|
||||
_request.Headers.Add("Cookie", "ABC");
|
||||
|
||||
// Prepare the formatters
|
||||
List<MediaTypeFormatter> formatters = new List<MediaTypeFormatter>();
|
||||
formatters.Add(new JsonMediaTypeFormatter());
|
||||
formatters.Add(new XmlMediaTypeFormatter());
|
||||
PlainTextFormatter frmtr = new PlainTextFormatter();
|
||||
frmtr.SupportedMediaTypes.Clear();
|
||||
frmtr.MediaTypeMappings.Clear();
|
||||
frmtr.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/xml"));
|
||||
frmtr.MediaTypeMappings.Add(new MyMediaTypeMapping(new MediaTypeHeaderValue(("application/xml"))));
|
||||
formatters.Add(frmtr);
|
||||
|
||||
// Act
|
||||
var result = _negotiator.Negotiate(typeof(string), _request, formatters);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("application/xml", result.MediaType.MediaType);
|
||||
Assert.IsType<PlainTextFormatter>(result.Formatter);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
[Fact]
|
||||
public void Negotiate_ForRequestReturnsFirstMatchingFormatter()
|
||||
{
|
||||
MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("application/myMediaType");
|
||||
|
||||
MediaTypeFormatter formatter1 = new MockMediaTypeFormatter()
|
||||
{
|
||||
CanWriteTypeCallback = (Type t) => false
|
||||
};
|
||||
|
||||
MediaTypeFormatter formatter2 = new MockMediaTypeFormatter()
|
||||
{
|
||||
CanWriteTypeCallback = (Type t) => true
|
||||
};
|
||||
|
||||
formatter2.SupportedMediaTypes.Add(mediaType);
|
||||
|
||||
MediaTypeFormatterCollection collection = new MediaTypeFormatterCollection(
|
||||
new MediaTypeFormatter[]
|
||||
{
|
||||
formatter1,
|
||||
formatter2
|
||||
});
|
||||
|
||||
_request.Content = new StringContent("test", Encoding.UTF8, mediaType.MediaType);
|
||||
|
||||
var result = _negotiator.Negotiate(typeof(string), _request, collection);
|
||||
Assert.Same(formatter2, result.Formatter);
|
||||
new MediaTypeAssert().AreEqual(mediaType, result.MediaType, "Expected the formatter's media type to be returned.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Negotiate_SelectsJsonAsDefaultFormatter()
|
||||
{
|
||||
// Arrange
|
||||
_request.Content = new StringContent("test");
|
||||
|
||||
// Act
|
||||
var result = _negotiator.Negotiate(typeof(string), _request, new MediaTypeFormatterCollection());
|
||||
|
||||
// Assert
|
||||
Assert.IsType<JsonMediaTypeFormatter>(result.Formatter);
|
||||
Assert.Equal(MediaTypeConstants.ApplicationJsonMediaType.MediaType, result.MediaType.MediaType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Negotiate_SelectsXmlFormatter_ForXhrRequestThatAcceptsXml()
|
||||
{
|
||||
// Arrange
|
||||
_request.Content = new StringContent("test");
|
||||
_request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml"));
|
||||
_request.Headers.Add("x-requested-with", "XMLHttpRequest");
|
||||
|
||||
// Act
|
||||
var result = _negotiator.Negotiate(typeof(string), _request, new MediaTypeFormatterCollection());
|
||||
|
||||
// Assert
|
||||
Assert.Equal("application/xml", result.MediaType.MediaType);
|
||||
Assert.IsType<XmlMediaTypeFormatter>(result.Formatter);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Negotiate_SelectsJsonFormatter_ForXhrRequestThatDoesNotSpecifyAcceptHeaders()
|
||||
{
|
||||
// Arrange
|
||||
_request.Content = new StringContent("test");
|
||||
_request.Headers.Add("x-requested-with", "XMLHttpRequest");
|
||||
|
||||
// Act
|
||||
var result = _negotiator.Negotiate(typeof(string), _request, new MediaTypeFormatterCollection());
|
||||
|
||||
// Assert
|
||||
Assert.Equal("application/json", result.MediaType.MediaType);
|
||||
Assert.IsType<JsonMediaTypeFormatter>(result.Formatter);
|
||||
}
|
||||
|
||||
#if !ASPNETCORE50
|
||||
|
||||
[Fact]
|
||||
public void Negotiate_RespectsFormatterOrdering_ForXhrRequestThatDoesNotSpecifyAcceptHeaders()
|
||||
{
|
||||
// Arrange
|
||||
_request.Content = new StringContent("test");
|
||||
_request.Headers.Add("x-requested-with", "XMLHttpRequest");
|
||||
|
||||
MediaTypeFormatterCollection formatters = new MediaTypeFormatterCollection(new MediaTypeFormatter[]
|
||||
{
|
||||
new XmlMediaTypeFormatter(),
|
||||
new JsonMediaTypeFormatter(),
|
||||
new FormUrlEncodedMediaTypeFormatter()
|
||||
});
|
||||
|
||||
// Act
|
||||
var result = _negotiator.Negotiate(typeof(string), _request, formatters);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("application/json", result.MediaType.MediaType);
|
||||
Assert.IsType<JsonMediaTypeFormatter>(result.Formatter);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
[Fact]
|
||||
public void Negotiate_SelectsJsonFormatter_ForXHRAndJsonValueResponse()
|
||||
{
|
||||
// Arrange
|
||||
_request.Content = new StringContent("test");
|
||||
_request.Headers.Add("x-requested-with", "XMLHttpRequest");
|
||||
|
||||
// Act
|
||||
var result = _negotiator.Negotiate(typeof(JToken), _request, new MediaTypeFormatterCollection());
|
||||
|
||||
Assert.Equal("application/json", result.MediaType.MediaType);
|
||||
Assert.IsType<JsonMediaTypeFormatter>(result.Formatter);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Negotiate_SelectsJsonFormatter_ForXHRAndMatchAllAcceptHeader()
|
||||
{
|
||||
// Accept
|
||||
_request.Content = new StringContent("test");
|
||||
_request.Headers.Add("x-requested-with", "XMLHttpRequest");
|
||||
_request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("*/*"));
|
||||
|
||||
// Act
|
||||
var result = _negotiator.Negotiate(typeof(string), _request, new MediaTypeFormatterCollection());
|
||||
|
||||
// Assert
|
||||
Assert.Equal("application/json", result.MediaType.MediaType);
|
||||
Assert.IsType<JsonMediaTypeFormatter>(result.Formatter);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Negotiate_UsesRequestedFormatterForXHRAndMatchAllPlusOtherAcceptHeader()
|
||||
{
|
||||
// Arrange
|
||||
_request.Content = new StringContent("test");
|
||||
_request.Headers.Add("x-requested-with", "XMLHttpRequest");
|
||||
_request.Headers.Accept.ParseAdd("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); // XHR header sent by Firefox 3b5
|
||||
|
||||
// Act
|
||||
var result = _negotiator.Negotiate(typeof(string), _request, new MediaTypeFormatterCollection());
|
||||
|
||||
// Assert
|
||||
Assert.Equal("application/xml", result.MediaType.MediaType);
|
||||
Assert.IsType<XmlMediaTypeFormatter>(result.Formatter);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public void Negotiate_ObservesExcludeMatchOnTypeOnly(bool excludeMatchOnTypeOnly)
|
||||
{
|
||||
// Arrange
|
||||
MockContentNegotiator negotiator = new MockContentNegotiator(excludeMatchOnTypeOnly);
|
||||
_request.Content = new StringContent("test");
|
||||
_request.Headers.Accept.ParseAdd("text/html");
|
||||
|
||||
// Act
|
||||
var result = negotiator.Negotiate(typeof(string), _request, new MediaTypeFormatterCollection());
|
||||
|
||||
// Assert
|
||||
if (excludeMatchOnTypeOnly)
|
||||
{
|
||||
Assert.Null(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("application/json", result.MediaType.MediaType);
|
||||
}
|
||||
}
|
||||
|
||||
#if !ASPNETCORE50
|
||||
|
||||
[Fact]
|
||||
public void MatchMediaTypeMapping_ReturnsMatch()
|
||||
{
|
||||
// Arrange
|
||||
MockContentNegotiator negotiator = new MockContentNegotiator();
|
||||
|
||||
HttpRequestMessage request = new HttpRequestMessage();
|
||||
MediaTypeHeaderValue mappingMediatype = MediaTypeHeaderValue.Parse("application/other");
|
||||
MockMediaTypeMapping mockMediaTypeMapping = new MockMediaTypeMapping(mappingMediatype, 0.75);
|
||||
|
||||
MockMediaTypeFormatter formatter = new MockMediaTypeFormatter();
|
||||
formatter.MediaTypeMappings.Add(mockMediaTypeMapping);
|
||||
|
||||
// Act
|
||||
MediaTypeFormatterMatch match = negotiator.MatchMediaTypeMapping(request, formatter);
|
||||
|
||||
// Assert
|
||||
Assert.True(mockMediaTypeMapping.WasInvoked);
|
||||
Assert.Same(request, mockMediaTypeMapping.Request);
|
||||
|
||||
Assert.Same(formatter, match.Formatter);
|
||||
Assert.Equal(mockMediaTypeMapping.MediaType, match.MediaType);
|
||||
Assert.Equal(mockMediaTypeMapping.MatchQuality, match.Quality);
|
||||
Assert.Equal(MediaTypeFormatterMatchRanking.MatchOnRequestWithMediaTypeMapping, match.Ranking);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
[Theory]
|
||||
[MemberData("MatchAcceptHeaderData")]
|
||||
public void MatchAcceptHeader_ReturnsMatch(string[] acceptHeaders, string[] supportedMediaTypes, string expectedMediaType, double expectedQuality, int ranking)
|
||||
{
|
||||
// Arrange
|
||||
MockContentNegotiator negotiator = new MockContentNegotiator();
|
||||
|
||||
List<MediaTypeWithQualityHeaderValue> unsortedAcceptHeaders = acceptHeaders.Select(a => MediaTypeWithQualityHeaderValue.Parse(a)).ToList();
|
||||
IEnumerable<MediaTypeWithQualityHeaderValue> sortedAcceptHeaders = negotiator.SortMediaTypeWithQualityHeaderValuesByQFactor(unsortedAcceptHeaders);
|
||||
|
||||
MockMediaTypeFormatter formatter = new MockMediaTypeFormatter();
|
||||
foreach (string supportedMediaType in supportedMediaTypes)
|
||||
{
|
||||
formatter.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(supportedMediaType));
|
||||
}
|
||||
|
||||
// Act
|
||||
MediaTypeFormatterMatch match = negotiator.MatchAcceptHeader(sortedAcceptHeaders, formatter);
|
||||
|
||||
// Assert
|
||||
if (expectedMediaType == null)
|
||||
{
|
||||
Assert.Null(match);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Same(formatter, match.Formatter);
|
||||
Assert.Equal(MediaTypeHeaderValue.Parse(expectedMediaType), match.MediaType);
|
||||
Assert.Equal(expectedQuality, match.Quality);
|
||||
Assert.Equal(ranking, (int)match.Ranking);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData("MatchRequestMediaTypeData")]
|
||||
public void MatchRequestMediaType_ReturnsMatch(string requestMediaType, string[] supportedMediaTypes, string expectedMediaType)
|
||||
{
|
||||
// Arrange
|
||||
MockContentNegotiator negotiator = new MockContentNegotiator();
|
||||
|
||||
HttpRequestMessage request = new HttpRequestMessage();
|
||||
request.Content = new StringContent(String.Empty);
|
||||
request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(requestMediaType);
|
||||
|
||||
MockMediaTypeFormatter formatter = new MockMediaTypeFormatter();
|
||||
foreach (string supportedMediaType in supportedMediaTypes)
|
||||
{
|
||||
formatter.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(supportedMediaType));
|
||||
}
|
||||
|
||||
// Act
|
||||
MediaTypeFormatterMatch match = negotiator.MatchRequestMediaType(request, formatter);
|
||||
|
||||
// Assert
|
||||
if (expectedMediaType == null)
|
||||
{
|
||||
Assert.Null(match);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Same(formatter, match.Formatter);
|
||||
Assert.Equal(MediaTypeHeaderValue.Parse(expectedMediaType), match.MediaType);
|
||||
Assert.Equal(1.0, match.Quality);
|
||||
Assert.Equal(MediaTypeFormatterMatchRanking.MatchOnRequestMediaType, match.Ranking);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData("ShouldMatchOnTypeData")]
|
||||
public void ShouldMatchOnType_ReturnsExpectedResult(bool excludeMatchOnType, string[] acceptHeaders, bool expectedResult)
|
||||
{
|
||||
// Arrange
|
||||
MockContentNegotiator negotiator = new MockContentNegotiator(excludeMatchOnType);
|
||||
List<MediaTypeWithQualityHeaderValue> unsortedAcceptHeaders = acceptHeaders.Select(a => MediaTypeWithQualityHeaderValue.Parse(a)).ToList();
|
||||
IEnumerable<MediaTypeWithQualityHeaderValue> sortedAcceptHeaders = negotiator.SortMediaTypeWithQualityHeaderValuesByQFactor(unsortedAcceptHeaders);
|
||||
|
||||
// Act
|
||||
bool result = negotiator.ShouldMatchOnType(sortedAcceptHeaders);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedResult, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData("MatchTypeData")]
|
||||
public void MatchType_ReturnsMatch(string[] supportedMediaTypes, string expectedMediaType)
|
||||
{
|
||||
// Arrange
|
||||
MockContentNegotiator negotiator = new MockContentNegotiator();
|
||||
|
||||
MockMediaTypeFormatter formatter = new MockMediaTypeFormatter();
|
||||
foreach (string supportedMediaType in supportedMediaTypes)
|
||||
{
|
||||
formatter.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(supportedMediaType));
|
||||
}
|
||||
|
||||
// Act
|
||||
MediaTypeFormatterMatch match = negotiator.MatchType(typeof(object), formatter);
|
||||
|
||||
// Assert
|
||||
Assert.Same(formatter, match.Formatter);
|
||||
Assert.Equal(MediaTypeHeaderValue.Parse(expectedMediaType), match.MediaType);
|
||||
Assert.Equal(1.0, match.Quality);
|
||||
Assert.Equal(MediaTypeFormatterMatchRanking.MatchOnCanWriteType, match.Ranking);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData("SelectResponseMediaTypeData")]
|
||||
public void SelectResponseMediaTypeFormatter_SelectsMediaType(ICollection<MediaTypeFormatterMatch> matches, MediaTypeFormatterMatch expectedWinner)
|
||||
{
|
||||
// Arrange
|
||||
MockContentNegotiator negotiator = new MockContentNegotiator();
|
||||
|
||||
// Act
|
||||
MediaTypeFormatterMatch actualWinner = negotiator.SelectResponseMediaTypeFormatter(matches);
|
||||
|
||||
// Assert
|
||||
Assert.Same(expectedWinner, actualWinner);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData("SelectResponseCharacterEncodingData")]
|
||||
public void SelectResponseCharacterEncoding_SelectsEncoding(string[] acceptCharsetHeaders, string requestEncoding, string[] supportedEncodings, string expectedEncoding)
|
||||
{
|
||||
// Arrange
|
||||
MockContentNegotiator negotiator = new MockContentNegotiator();
|
||||
|
||||
HttpRequestMessage request = new HttpRequestMessage();
|
||||
foreach (string acceptCharsetHeader in acceptCharsetHeaders)
|
||||
{
|
||||
request.Headers.AcceptCharset.Add(StringWithQualityHeaderValue.Parse(acceptCharsetHeader));
|
||||
}
|
||||
|
||||
if (requestEncoding != null)
|
||||
{
|
||||
Encoding reqEncoding = Encoding.GetEncoding(requestEncoding);
|
||||
StringContent content = new StringContent("", reqEncoding, "text/plain");
|
||||
request.Content = content;
|
||||
}
|
||||
|
||||
MockMediaTypeFormatter formatter = new MockMediaTypeFormatter() { CallBase = true };
|
||||
foreach (string supportedEncoding in supportedEncodings)
|
||||
{
|
||||
formatter.SupportedEncodings.Add(Encoding.GetEncoding(supportedEncoding));
|
||||
}
|
||||
|
||||
// Act
|
||||
Encoding actualEncoding = negotiator.SelectResponseCharacterEncoding(request, formatter);
|
||||
|
||||
// Assert
|
||||
if (expectedEncoding == null)
|
||||
{
|
||||
Assert.Null(actualEncoding);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Equal(Encoding.GetEncoding(expectedEncoding), actualEncoding);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[TestDataSet(typeof(DefaultContentNegotiatorTests), nameof(MediaTypeWithQualityHeaderValueComparerTestsBeforeAfterSortedValues))]
|
||||
public void SortMediaTypeWithQualityHeaderValuesByQFactor_SortsCorrectly(IEnumerable<string> unsorted, IEnumerable<string> expectedSorted)
|
||||
{
|
||||
// Arrange
|
||||
MockContentNegotiator negotiator = new MockContentNegotiator();
|
||||
|
||||
List<MediaTypeWithQualityHeaderValue> unsortedValues =
|
||||
new List<MediaTypeWithQualityHeaderValue>(unsorted.Select(u => MediaTypeWithQualityHeaderValue.Parse(u)));
|
||||
|
||||
List<MediaTypeWithQualityHeaderValue> expectedSortedValues =
|
||||
new List<MediaTypeWithQualityHeaderValue>(expectedSorted.Select(u => MediaTypeWithQualityHeaderValue.Parse(u)));
|
||||
|
||||
// Act
|
||||
IEnumerable<MediaTypeWithQualityHeaderValue> actualSorted = negotiator.SortMediaTypeWithQualityHeaderValuesByQFactor(unsortedValues);
|
||||
|
||||
// Assert
|
||||
Assert.True(expectedSortedValues.SequenceEqual(actualSorted));
|
||||
}
|
||||
|
||||
public static TheoryData<string[], string[]> MediaTypeWithQualityHeaderValueComparerTestsBeforeAfterSortedValues
|
||||
{
|
||||
get
|
||||
{
|
||||
return new TheoryData<string[], string[]>
|
||||
{
|
||||
{
|
||||
new string[]
|
||||
{
|
||||
"application/*",
|
||||
"text/plain",
|
||||
"text/plain;q=1.0",
|
||||
"text/plain",
|
||||
"text/plain;q=0",
|
||||
"*/*;q=0.8",
|
||||
"*/*;q=1",
|
||||
"text/*;q=1",
|
||||
"text/plain;q=0.8",
|
||||
"text/*;q=0.8",
|
||||
"text/*;q=0.6",
|
||||
"text/*;q=1.0",
|
||||
"*/*;q=0.4",
|
||||
"text/plain;q=0.6",
|
||||
"text/xml",
|
||||
},
|
||||
new string[]
|
||||
{
|
||||
"text/plain",
|
||||
"text/plain;q=1.0",
|
||||
"text/plain",
|
||||
"text/xml",
|
||||
"application/*",
|
||||
"text/*;q=1",
|
||||
"text/*;q=1.0",
|
||||
"*/*;q=1",
|
||||
"text/plain;q=0.8",
|
||||
"text/*;q=0.8",
|
||||
"*/*;q=0.8",
|
||||
"text/plain;q=0.6",
|
||||
"text/*;q=0.6",
|
||||
"*/*;q=0.4",
|
||||
"text/plain;q=0",
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[TestDataSet(typeof(DefaultContentNegotiatorTests), nameof(StringWithQualityHeaderValueComparerTestsBeforeAfterSortedValues))]
|
||||
public void SortStringWithQualityHeaderValuesByQFactor_SortsCorrectly(IEnumerable<string> unsorted, IEnumerable<string> expectedSorted)
|
||||
{
|
||||
// Arrange
|
||||
MockContentNegotiator negotiator = new MockContentNegotiator();
|
||||
|
||||
List<StringWithQualityHeaderValue> unsortedValues =
|
||||
new List<StringWithQualityHeaderValue>(unsorted.Select(u => StringWithQualityHeaderValue.Parse(u)));
|
||||
|
||||
List<StringWithQualityHeaderValue> expectedSortedValues =
|
||||
new List<StringWithQualityHeaderValue>(expectedSorted.Select(u => StringWithQualityHeaderValue.Parse(u)));
|
||||
|
||||
// Act
|
||||
IEnumerable<StringWithQualityHeaderValue> actualSorted = negotiator.SortStringWithQualityHeaderValuesByQFactor(unsortedValues);
|
||||
|
||||
// Assert
|
||||
Assert.True(expectedSortedValues.SequenceEqual(actualSorted));
|
||||
}
|
||||
|
||||
public static TheoryData<string[], string[]> StringWithQualityHeaderValueComparerTestsBeforeAfterSortedValues
|
||||
{
|
||||
get
|
||||
{
|
||||
return new TheoryData<string[], string[]>
|
||||
{
|
||||
{
|
||||
new string[]
|
||||
{
|
||||
"text",
|
||||
"text;q=1.0",
|
||||
"text",
|
||||
"text;q=0",
|
||||
"*;q=0.8",
|
||||
"*;q=1",
|
||||
"text;q=0.8",
|
||||
"*;q=0.6",
|
||||
"text;q=1.0",
|
||||
"*;q=0.4",
|
||||
"text;q=0.6",
|
||||
},
|
||||
new string[]
|
||||
{
|
||||
"text",
|
||||
"text;q=1.0",
|
||||
"text",
|
||||
"text;q=1.0",
|
||||
"*;q=1",
|
||||
"text;q=0.8",
|
||||
"*;q=0.8",
|
||||
"text;q=0.6",
|
||||
"*;q=0.6",
|
||||
"*;q=0.4",
|
||||
"text;q=0",
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData("UpdateBestMatchData")]
|
||||
public void UpdateBestMatch_SelectsCorrectly(MediaTypeFormatterMatch current, MediaTypeFormatterMatch replacement, bool currentWins)
|
||||
{
|
||||
// Arrange
|
||||
MockContentNegotiator negotiator = new MockContentNegotiator();
|
||||
|
||||
// Act
|
||||
MediaTypeFormatterMatch actualResult = negotiator.UpdateBestMatch(current, replacement);
|
||||
|
||||
// Assert
|
||||
if (currentWins)
|
||||
{
|
||||
Assert.Same(current, actualResult);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Same(replacement, actualResult);
|
||||
}
|
||||
}
|
||||
|
||||
private class PlainTextFormatter : MediaTypeFormatter
|
||||
{
|
||||
public override bool CanReadType(Type type)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool CanWriteType(Type type)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
#if !ASPNETCORE50
|
||||
|
||||
private class MyMediaTypeMapping : MediaTypeMapping
|
||||
{
|
||||
public MyMediaTypeMapping(MediaTypeHeaderValue mediaType)
|
||||
: base(mediaType)
|
||||
{
|
||||
}
|
||||
|
||||
public override double TryMatchMediaType(HttpRequestMessage request)
|
||||
{
|
||||
if (request.Headers.Contains("Cookie"))
|
||||
{
|
||||
return 1.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -108,6 +108,8 @@ namespace System.Web.Http.Dispatcher
|
|||
Assert.DoesNotContain("OH NO", modelStateError["[2].Name"] as IEnumerable<string>);
|
||||
}
|
||||
|
||||
#if !ASPNETCORE50
|
||||
|
||||
[Fact]
|
||||
public void HttpError_Roundtrips_WithJsonFormatter()
|
||||
{
|
||||
|
|
@ -196,6 +198,8 @@ namespace System.Web.Http.Dispatcher
|
|||
serializedError);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
[Fact]
|
||||
public void GetPropertyValue_GetsValue_IfTypeMatches()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,13 +8,46 @@ using Microsoft.AspNet.Http;
|
|||
using Microsoft.AspNet.Mvc.WebApiCompatShim;
|
||||
using Microsoft.AspNet.PipelineCore;
|
||||
using Microsoft.Framework.OptionsModel;
|
||||
#if !ASPNETCORE50
|
||||
using Moq;
|
||||
#endif
|
||||
using Xunit;
|
||||
|
||||
namespace System.Net.Http
|
||||
{
|
||||
public class HttpRequestMessageExtensionsTest
|
||||
{
|
||||
[Fact]
|
||||
public void CreateResponse_MatchingMediaType_WhenMediaTypeStringIsInvalidFormat_Throws()
|
||||
{
|
||||
HttpRequestMessage request = CreateRequest(new DefaultHttpContext());
|
||||
|
||||
var ex = Assert.Throws<FormatException>(
|
||||
() => request.CreateResponse(HttpStatusCode.OK, CreateValue(), "foo/bar; param=value"));
|
||||
|
||||
Assert.Equal("The format of value 'foo/bar; param=value' is invalid.", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateResponse_MatchingMediaType_WhenRequestDoesNotHaveHttpContextThrows()
|
||||
{
|
||||
HttpRequestMessage request = CreateRequest(null);
|
||||
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
var ex = Assert.Throws<InvalidOperationException>(
|
||||
() => request.CreateResponse(HttpStatusCode.OK, CreateValue(), mediaType: "foo/bar"));
|
||||
|
||||
Assert.Equal(
|
||||
"The HttpRequestMessage instance is not properly initialized. " +
|
||||
"Use HttpRequestMessageHttpContextExtensions.GetHttpRequestMessage to create an HttpRequestMessage " +
|
||||
"for the current request.",
|
||||
ex.Message);
|
||||
}
|
||||
|
||||
#if !ASPNETCORE50
|
||||
|
||||
[Fact]
|
||||
public void CreateResponse_DoingConneg_OnlyContent_RetrievesContentNegotiatorFromServices()
|
||||
{
|
||||
|
|
@ -110,36 +143,6 @@ namespace System.Net.Http
|
|||
Assert.Same(formatter, objectContent.Formatter);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void CreateResponse_MatchingMediaType_WhenMediaTypeStringIsInvalidFormat_Throws()
|
||||
{
|
||||
HttpRequestMessage request = CreateRequest(new DefaultHttpContext());
|
||||
|
||||
var ex = Assert.Throws<FormatException>(
|
||||
() => request.CreateResponse(HttpStatusCode.OK, CreateValue(), "foo/bar; param=value"));
|
||||
|
||||
Assert.Equal("The format of value 'foo/bar; param=value' is invalid.", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateResponse_MatchingMediaType_WhenRequestDoesNotHaveHttpContextThrows()
|
||||
{
|
||||
HttpRequestMessage request = CreateRequest(null);
|
||||
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
var ex = Assert.Throws<InvalidOperationException>(
|
||||
() => request.CreateResponse(HttpStatusCode.OK, CreateValue(), mediaType: "foo/bar"));
|
||||
|
||||
Assert.Equal(
|
||||
"The HttpRequestMessage instance is not properly initialized. " +
|
||||
"Use HttpRequestMessageHttpContextExtensions.GetHttpRequestMessage to create an HttpRequestMessage " +
|
||||
"for the current request.",
|
||||
ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateResponse_MatchingMediaType_WhenMediaTypeDoesNotMatch_Throws()
|
||||
{
|
||||
|
|
@ -299,20 +302,8 @@ namespace System.Net.Http
|
|||
Assert.Same(expectedContentRange, response.Content.Headers.ContentRange);
|
||||
}
|
||||
|
||||
private static HttpRequestMessage CreateRequest(HttpContext context)
|
||||
{
|
||||
var request = new HttpRequestMessage();
|
||||
request.Properties.Add(nameof(HttpContext), context);
|
||||
return request;
|
||||
}
|
||||
|
||||
private static object CreateValue()
|
||||
{
|
||||
return new object();
|
||||
}
|
||||
|
||||
private static IServiceProvider CreateServices(
|
||||
IContentNegotiator contentNegotiator = null,
|
||||
IContentNegotiator contentNegotiator = null,
|
||||
MediaTypeFormatter formatter = null)
|
||||
{
|
||||
var options = new WebApiCompatShimOptions();
|
||||
|
|
@ -343,5 +334,17 @@ namespace System.Net.Http
|
|||
|
||||
return services.Object;
|
||||
}
|
||||
#endif
|
||||
private static object CreateValue()
|
||||
{
|
||||
return new object();
|
||||
}
|
||||
|
||||
private static HttpRequestMessage CreateRequest(HttpContext context)
|
||||
{
|
||||
var request = new HttpRequestMessage();
|
||||
request.Properties.Add(nameof(HttpContext), context);
|
||||
return request;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,13 +6,28 @@ using System.Net;
|
|||
using System.Net.Http;
|
||||
using Microsoft.AspNet.PipelineCore;
|
||||
using Microsoft.AspNet.Routing;
|
||||
#if !ASPNETCORE50
|
||||
using Moq;
|
||||
#endif
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.WebApiCompatShim
|
||||
{
|
||||
public class HttpResponseExceptionActionFilterTest
|
||||
{
|
||||
[Fact]
|
||||
public void OrderIsSetToMaxValue()
|
||||
{
|
||||
// Arrange
|
||||
var filter = new HttpResponseExceptionActionFilter();
|
||||
var expectedFilterOrder = int.MaxValue - 10;
|
||||
|
||||
// Act & Assert
|
||||
Assert.Equal(expectedFilterOrder, filter.Order);
|
||||
}
|
||||
|
||||
#if !ASPNETCORE50
|
||||
|
||||
[Fact]
|
||||
public void OnActionExecuting_IsNoOp()
|
||||
{
|
||||
|
|
@ -32,17 +47,6 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
|
|||
Assert.Null(context.Result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OrderIsSetToMaxValue()
|
||||
{
|
||||
// Arrange
|
||||
var filter = new HttpResponseExceptionActionFilter();
|
||||
var expectedFilterOrder = int.MaxValue - 10;
|
||||
|
||||
// Act & Assert
|
||||
Assert.Equal(expectedFilterOrder, filter.Order);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnActionExecuted_HandlesExceptionAndReturnsObjectResult()
|
||||
{
|
||||
|
|
@ -71,5 +75,8 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
|
|||
Assert.Equal(context.HttpContext.GetHttpRequestMessage(), response.RequestMessage);
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
#if !ASPNETCORE50
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
|
|
@ -40,7 +42,8 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShimTest
|
|||
Object = outputValue,
|
||||
DeclaredType = outputType,
|
||||
ActionContext = new ActionContext(new DefaultHttpContext(), routeData: null, actionDescriptor: null)
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. 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.Net.Http.Headers;
|
||||
using System.Text;
|
||||
|
||||
namespace System.Net.Http.Formatting.Mocks
|
||||
{
|
||||
public class MockContentNegotiator : DefaultContentNegotiator
|
||||
{
|
||||
public MockContentNegotiator()
|
||||
{
|
||||
}
|
||||
|
||||
public MockContentNegotiator(bool excludeMatchOnTypeOnly)
|
||||
: base(excludeMatchOnTypeOnly)
|
||||
{
|
||||
}
|
||||
|
||||
public new Collection<MediaTypeFormatterMatch> ComputeFormatterMatches(Type type, HttpRequestMessage request, IEnumerable<MediaTypeFormatter> formatters)
|
||||
{
|
||||
return base.ComputeFormatterMatches(type, request, formatters);
|
||||
}
|
||||
|
||||
public new MediaTypeFormatterMatch SelectResponseMediaTypeFormatter(ICollection<MediaTypeFormatterMatch> matches)
|
||||
{
|
||||
return base.SelectResponseMediaTypeFormatter(matches);
|
||||
}
|
||||
|
||||
public new Encoding SelectResponseCharacterEncoding(HttpRequestMessage request, MediaTypeFormatter formatter)
|
||||
{
|
||||
return base.SelectResponseCharacterEncoding(request, formatter);
|
||||
}
|
||||
|
||||
#if !ASPNETCORE50
|
||||
|
||||
public new MediaTypeFormatterMatch MatchMediaTypeMapping(HttpRequestMessage request, MediaTypeFormatter formatter)
|
||||
{
|
||||
return base.MatchMediaTypeMapping(request, formatter);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
public new MediaTypeFormatterMatch MatchAcceptHeader(IEnumerable<MediaTypeWithQualityHeaderValue> sortedAcceptValues, MediaTypeFormatter formatter)
|
||||
{
|
||||
return base.MatchAcceptHeader(sortedAcceptValues, formatter);
|
||||
}
|
||||
|
||||
public new MediaTypeFormatterMatch MatchRequestMediaType(HttpRequestMessage request, MediaTypeFormatter formatter)
|
||||
{
|
||||
return base.MatchRequestMediaType(request, formatter);
|
||||
}
|
||||
|
||||
public new bool ShouldMatchOnType(IEnumerable<MediaTypeWithQualityHeaderValue> sortedAcceptValues)
|
||||
{
|
||||
return base.ShouldMatchOnType(sortedAcceptValues);
|
||||
}
|
||||
|
||||
public new MediaTypeFormatterMatch MatchType(Type type, MediaTypeFormatter formatter)
|
||||
{
|
||||
return base.MatchType(type, formatter);
|
||||
}
|
||||
|
||||
public new IEnumerable<MediaTypeWithQualityHeaderValue> SortMediaTypeWithQualityHeaderValuesByQFactor(ICollection<MediaTypeWithQualityHeaderValue> headerValues)
|
||||
{
|
||||
return base.SortMediaTypeWithQualityHeaderValuesByQFactor(headerValues);
|
||||
}
|
||||
|
||||
public new IEnumerable<StringWithQualityHeaderValue> SortStringWithQualityHeaderValuesByQFactor(ICollection<StringWithQualityHeaderValue> headerValues)
|
||||
{
|
||||
return base.SortStringWithQualityHeaderValuesByQFactor(headerValues);
|
||||
}
|
||||
|
||||
public new MediaTypeFormatterMatch UpdateBestMatch(MediaTypeFormatterMatch current, MediaTypeFormatterMatch potentialReplacement)
|
||||
{
|
||||
return base.UpdateBestMatch(current, potentialReplacement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. 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;
|
||||
using System.Text;
|
||||
|
||||
namespace System.Net.Http.Formatting.Mocks
|
||||
{
|
||||
public class MockMediaTypeFormatter : MediaTypeFormatter
|
||||
{
|
||||
private bool _canWriteAnyTypes = true;
|
||||
public bool CallBase { get; set; }
|
||||
public Func<Type, bool> CanReadTypeCallback { get; set; }
|
||||
public Func<Type, bool> CanWriteTypeCallback { get; set; }
|
||||
|
||||
public bool CanWriteAnyTypesReturn
|
||||
{
|
||||
get { return _canWriteAnyTypes; }
|
||||
set { _canWriteAnyTypes = value; }
|
||||
}
|
||||
|
||||
public override bool CanReadType(Type type)
|
||||
{
|
||||
if (!CallBase && CanReadTypeCallback == null)
|
||||
{
|
||||
throw new InvalidOperationException("CallBase or CanReadTypeCallback must be set first.");
|
||||
}
|
||||
|
||||
return CanReadTypeCallback != null ? CanReadTypeCallback(type) : true;
|
||||
}
|
||||
|
||||
public override bool CanWriteType(Type type)
|
||||
{
|
||||
if (!CallBase && CanWriteTypeCallback == null)
|
||||
{
|
||||
throw new InvalidOperationException("CallBase or CanWriteTypeCallback must be set first.");
|
||||
}
|
||||
|
||||
return CanWriteTypeCallback != null ? CanWriteTypeCallback(type) : true;
|
||||
}
|
||||
|
||||
public new Encoding SelectCharacterEncoding(HttpContentHeaders contentHeaders)
|
||||
{
|
||||
return base.SelectCharacterEncoding(contentHeaders);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
#if !ASPNETCORE50
|
||||
|
||||
using System.Net.Http.Headers;
|
||||
namespace System.Net.Http.Formatting.Mocks
|
||||
{
|
||||
public class MockMediaTypeMapping : MediaTypeMapping
|
||||
{
|
||||
public MockMediaTypeMapping(string mediaType, double matchQuality)
|
||||
: base(mediaType)
|
||||
{
|
||||
MatchQuality = matchQuality;
|
||||
}
|
||||
|
||||
public MockMediaTypeMapping(MediaTypeHeaderValue mediaType, double matchQuality)
|
||||
: base(mediaType)
|
||||
{
|
||||
MatchQuality = matchQuality;
|
||||
}
|
||||
|
||||
public double MatchQuality { get; private set; }
|
||||
|
||||
public HttpRequestMessage Request { get; private set; }
|
||||
|
||||
public bool WasInvoked { get; private set; }
|
||||
|
||||
public override double TryMatchMediaType(HttpRequestMessage request)
|
||||
{
|
||||
WasInvoked = true;
|
||||
Request = request;
|
||||
return MatchQuality;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.TestCommon.Types
|
||||
{
|
||||
[Flags]
|
||||
public enum FlagsEnum
|
||||
{
|
||||
One = 0x1,
|
||||
Two = 0x2,
|
||||
Four = 0x4
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.TestCommon.Types
|
||||
{
|
||||
public enum LongEnum : long
|
||||
{
|
||||
FirstLong,
|
||||
SecondLong,
|
||||
ThirdLong,
|
||||
FourthLong
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Net.Http.Headers;
|
||||
using Xunit;
|
||||
using Microsoft.AspNet.Mvc;
|
||||
|
||||
namespace Microsoft.TestCommon
|
||||
{
|
||||
public class MediaTypeAssert
|
||||
{
|
||||
private static readonly MediaTypeAssert singleton = new MediaTypeAssert();
|
||||
|
||||
public static MediaTypeAssert Singleton { get { return singleton; } }
|
||||
|
||||
public void AreEqual(MediaTypeHeaderValue expected, MediaTypeHeaderValue actual, string errorMessage)
|
||||
{
|
||||
if (expected != null || actual != null)
|
||||
{
|
||||
Assert.NotNull(expected);
|
||||
Assert.Equal(0, new MediaTypeHeaderValueComparer().Compare(expected, actual));
|
||||
}
|
||||
}
|
||||
|
||||
public void AreEqual(MediaTypeHeaderValue expected, string actual, string errorMessage)
|
||||
{
|
||||
if (expected != null || !String.IsNullOrEmpty(actual))
|
||||
{
|
||||
MediaTypeHeaderValue actualMediaType = new MediaTypeHeaderValue(actual);
|
||||
Assert.NotNull(expected);
|
||||
Assert.Equal(0, new MediaTypeHeaderValueComparer().Compare(expected, actualMediaType));
|
||||
}
|
||||
}
|
||||
|
||||
public void AreEqual(string expected, string actual, string errorMessage)
|
||||
{
|
||||
if (!String.IsNullOrEmpty(expected) || !String.IsNullOrEmpty(actual))
|
||||
{
|
||||
Assert.NotNull(expected);
|
||||
MediaTypeHeaderValue expectedMediaType = new MediaTypeHeaderValue(expected);
|
||||
MediaTypeHeaderValue actualMediaType = new MediaTypeHeaderValue(actual);
|
||||
Assert.Equal(0, new MediaTypeHeaderValueComparer().Compare(expectedMediaType, actualMediaType));
|
||||
}
|
||||
}
|
||||
|
||||
public void AreEqual(string expected, MediaTypeHeaderValue actual, string errorMessage)
|
||||
{
|
||||
if (!String.IsNullOrEmpty(expected) || actual != null)
|
||||
{
|
||||
Assert.NotNull(expected);
|
||||
MediaTypeHeaderValue expectedMediaType = new MediaTypeHeaderValue(expected);
|
||||
Assert.Equal(0, new MediaTypeHeaderValueComparer().Compare(expectedMediaType, actual));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. 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
|
||||
/// http://www.iana.org/assignments/media-types/application.
|
||||
/// </remarks>
|
||||
public static MediaTypeHeaderValue ApplicationBsonMediaType
|
||||
{
|
||||
get { return _defaultApplicationBsonMediaType; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,195 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http.Headers;
|
||||
|
||||
namespace Microsoft.TestCommon
|
||||
{
|
||||
public class MediaTypeHeaderValueComparer : IComparer<MediaTypeHeaderValue>
|
||||
{
|
||||
private static readonly MediaTypeHeaderValueComparer mediaTypeComparer = new MediaTypeHeaderValueComparer();
|
||||
|
||||
public MediaTypeHeaderValueComparer()
|
||||
{
|
||||
}
|
||||
|
||||
public static MediaTypeHeaderValueComparer Comparer
|
||||
{
|
||||
get
|
||||
{
|
||||
return mediaTypeComparer;
|
||||
}
|
||||
}
|
||||
|
||||
public int Compare(MediaTypeHeaderValue mediaType1, MediaTypeHeaderValue mediaType2)
|
||||
{
|
||||
ParsedMediaTypeHeaderValue parsedMediaType1 = new ParsedMediaTypeHeaderValue(mediaType1);
|
||||
ParsedMediaTypeHeaderValue parsedMediaType2 = new ParsedMediaTypeHeaderValue(mediaType2);
|
||||
|
||||
int returnValue = CompareBasedOnQualityFactor(parsedMediaType1, parsedMediaType2);
|
||||
|
||||
if (returnValue == 0)
|
||||
{
|
||||
if (!String.Equals(parsedMediaType1.Type, parsedMediaType2.Type, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (parsedMediaType1.IsAllMediaRange)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
else if (parsedMediaType2.IsAllMediaRange)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else if (!String.Equals(parsedMediaType1.SubType, parsedMediaType2.SubType, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (parsedMediaType1.IsSubTypeMediaRange)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
else if (parsedMediaType2.IsSubTypeMediaRange)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!parsedMediaType1.HasNonQualityFactorParameter)
|
||||
{
|
||||
if (parsedMediaType2.HasNonQualityFactorParameter)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else if (!parsedMediaType2.HasNonQualityFactorParameter)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
private static int CompareBasedOnQualityFactor(ParsedMediaTypeHeaderValue parsedMediaType1, ParsedMediaTypeHeaderValue parsedMediaType2)
|
||||
{
|
||||
double qualityDifference = parsedMediaType1.QualityFactor - parsedMediaType2.QualityFactor;
|
||||
if (qualityDifference < 0)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
else if (qualityDifference > 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
internal class ParsedMediaTypeHeaderValue
|
||||
{
|
||||
private const string MediaRangeAsterisk = "*";
|
||||
private const char MediaTypeSubTypeDelimiter = '/';
|
||||
private const string QualityFactorParameterName = "q";
|
||||
private const double DefaultQualityFactor = 1.0;
|
||||
|
||||
private MediaTypeHeaderValue mediaType;
|
||||
private string type;
|
||||
private string subType;
|
||||
private bool? hasNonQualityFactorParameter;
|
||||
private double? qualityFactor;
|
||||
|
||||
public ParsedMediaTypeHeaderValue(MediaTypeHeaderValue mediaType)
|
||||
{
|
||||
this.mediaType = mediaType;
|
||||
string[] splitMediaType = mediaType.MediaType.Split(MediaTypeSubTypeDelimiter);
|
||||
this.type = splitMediaType[0];
|
||||
this.subType = splitMediaType[1];
|
||||
}
|
||||
|
||||
public string Type
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.type;
|
||||
}
|
||||
}
|
||||
|
||||
public string SubType
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.subType;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsAllMediaRange
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.IsSubTypeMediaRange && String.Equals(MediaRangeAsterisk, this.Type, StringComparison.Ordinal);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSubTypeMediaRange
|
||||
{
|
||||
get
|
||||
{
|
||||
return String.Equals(MediaRangeAsterisk, this.SubType, StringComparison.Ordinal);
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasNonQualityFactorParameter
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!this.hasNonQualityFactorParameter.HasValue)
|
||||
{
|
||||
this.hasNonQualityFactorParameter = false;
|
||||
foreach (NameValueHeaderValue param in this.mediaType.Parameters)
|
||||
{
|
||||
if (!String.Equals(QualityFactorParameterName, param.Name, StringComparison.Ordinal))
|
||||
{
|
||||
this.hasNonQualityFactorParameter = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.hasNonQualityFactorParameter.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public string CharSet
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.mediaType.CharSet;
|
||||
}
|
||||
}
|
||||
|
||||
public double QualityFactor
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!this.qualityFactor.HasValue)
|
||||
{
|
||||
MediaTypeWithQualityHeaderValue mediaTypeWithQuality = this.mediaType as MediaTypeWithQualityHeaderValue;
|
||||
if (mediaTypeWithQuality != null)
|
||||
{
|
||||
this.qualityFactor = mediaTypeWithQuality.Quality;
|
||||
}
|
||||
|
||||
if (!this.qualityFactor.HasValue)
|
||||
{
|
||||
this.qualityFactor = DefaultQualityFactor;
|
||||
}
|
||||
}
|
||||
|
||||
return this.qualityFactor.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.TestCommon
|
||||
{
|
||||
public class RefTypeTestData<T> : TestData<T> where T : class
|
||||
{
|
||||
private Func<IEnumerable<T>> testDataProvider;
|
||||
private Func<IEnumerable<T>> derivedTypeTestDataProvider;
|
||||
private Func<IEnumerable<T>> knownTypeTestDataProvider;
|
||||
|
||||
public RefTypeTestData(Func<IEnumerable<T>> testDataProvider)
|
||||
{
|
||||
if (testDataProvider == null)
|
||||
{
|
||||
throw new ArgumentNullException("testDataProvider");
|
||||
}
|
||||
|
||||
this.testDataProvider = testDataProvider;
|
||||
this.RegisterTestDataVariation(TestDataVariations.WithNull, this.Type, GetNullTestData);
|
||||
}
|
||||
|
||||
public RefTypeTestData(
|
||||
Func<IEnumerable<T>> testDataProvider,
|
||||
Func<IEnumerable<T>> derivedTypeTestDataProvider,
|
||||
Func<IEnumerable<T>> knownTypeTestDataProvider)
|
||||
: this(testDataProvider)
|
||||
{
|
||||
this.derivedTypeTestDataProvider = derivedTypeTestDataProvider;
|
||||
if (this.derivedTypeTestDataProvider != null)
|
||||
{
|
||||
this.RegisterTestDataVariation(TestDataVariations.AsDerivedType, this.Type, this.GetTestDataAsDerivedType);
|
||||
}
|
||||
|
||||
this.knownTypeTestDataProvider = knownTypeTestDataProvider;
|
||||
if (this.knownTypeTestDataProvider != null)
|
||||
{
|
||||
this.RegisterTestDataVariation(TestDataVariations.AsKnownType, this.Type, this.GetTestDataAsDerivedKnownType);
|
||||
}
|
||||
}
|
||||
|
||||
public T GetNullTestData()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public IEnumerable<T> GetTestDataAsDerivedType()
|
||||
{
|
||||
if (this.derivedTypeTestDataProvider != null)
|
||||
{
|
||||
return this.derivedTypeTestDataProvider();
|
||||
}
|
||||
|
||||
return Enumerable.Empty<T>();
|
||||
}
|
||||
|
||||
public IEnumerable<T> GetTestDataAsDerivedKnownType()
|
||||
{
|
||||
if (this.knownTypeTestDataProvider != null)
|
||||
{
|
||||
return this.knownTypeTestDataProvider();
|
||||
}
|
||||
|
||||
return Enumerable.Empty<T>();
|
||||
}
|
||||
|
||||
protected override IEnumerable<T> GetTypedTestData()
|
||||
{
|
||||
return this.testDataProvider();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.TestCommon.Types
|
||||
{
|
||||
public enum SimpleEnum
|
||||
{
|
||||
First,
|
||||
Second,
|
||||
Third,
|
||||
Fourth
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,441 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using Microsoft.TestCommon.Types;
|
||||
|
||||
namespace Microsoft.TestCommon
|
||||
{
|
||||
/// <summary>
|
||||
/// A base class for test data. A <see cref="TestData"/> instance is associated with a given type, and the <see cref="TestData"/> instance can
|
||||
/// provide instances of the given type to use as data in tests. The same <see cref="TestData"/> instance can also provide instances
|
||||
/// of types related to the given type, such as a <see cref="List<>"/> of the type. See the <see cref="TestDataVariations"/> enum for all the
|
||||
/// variations of test data that a <see cref="TestData"/> instance can provide.
|
||||
/// </summary>
|
||||
public abstract class TestData
|
||||
{
|
||||
/// <summary>
|
||||
/// Common <see cref="TestData"/> for a <see cref="char"/>.
|
||||
/// </summary>
|
||||
public static readonly ValueTypeTestData<char> CharTestData = new ValueTypeTestData<char>('a', Char.MinValue, Char.MaxValue);
|
||||
|
||||
/// <summary>
|
||||
/// Common <see cref="TestData"/> for a <see cref="int"/>.
|
||||
/// </summary>
|
||||
public static readonly ValueTypeTestData<int> IntTestData = new ValueTypeTestData<int>(-1, 0, 1, Int32.MinValue, Int32.MaxValue);
|
||||
|
||||
/// <summary>
|
||||
/// Common <see cref="TestData"/> for a <see cref="uint"/>.
|
||||
/// </summary>
|
||||
public static readonly ValueTypeTestData<uint> UintTestData = new ValueTypeTestData<uint>(0, 1, UInt32.MinValue, UInt32.MaxValue);
|
||||
|
||||
/// <summary>
|
||||
/// Common <see cref="TestData"/> for a <see cref="short"/>.
|
||||
/// </summary>
|
||||
public static readonly ValueTypeTestData<short> ShortTestData = new ValueTypeTestData<short>(-1, 0, 1, Int16.MinValue, Int16.MaxValue);
|
||||
|
||||
/// <summary>
|
||||
/// Common <see cref="TestData"/> for a <see cref="ushort"/>.
|
||||
/// </summary>
|
||||
public static readonly ValueTypeTestData<ushort> UshortTestData = new ValueTypeTestData<ushort>(0, 1, UInt16.MinValue, UInt16.MaxValue);
|
||||
|
||||
/// <summary>
|
||||
/// Common <see cref="TestData"/> for a <see cref="long"/>.
|
||||
/// </summary>
|
||||
public static readonly ValueTypeTestData<long> LongTestData = new ValueTypeTestData<long>(-1, 0, 1, Int64.MinValue, Int64.MaxValue);
|
||||
|
||||
/// <summary>
|
||||
/// Common <see cref="TestData"/> for a <see cref="ulong"/>.
|
||||
/// </summary>
|
||||
public static readonly ValueTypeTestData<ulong> UlongTestData = new ValueTypeTestData<ulong>(0, 1, UInt64.MinValue, UInt64.MaxValue);
|
||||
|
||||
/// <summary>
|
||||
/// Common <see cref="TestData"/> for a <see cref="byte"/>.
|
||||
/// </summary>
|
||||
public static readonly ValueTypeTestData<byte> ByteTestData = new ValueTypeTestData<byte>(0, 1, Byte.MinValue, Byte.MaxValue);
|
||||
|
||||
/// <summary>
|
||||
/// Common <see cref="TestData"/> for a <see cref="sbyte"/>.
|
||||
/// </summary>
|
||||
public static readonly ValueTypeTestData<sbyte> SByteTestData = new ValueTypeTestData<sbyte>(-1, 0, 1, SByte.MinValue, SByte.MaxValue);
|
||||
|
||||
/// <summary>
|
||||
/// Common <see cref="TestData"/> for a <see cref="bool"/>.
|
||||
/// </summary>
|
||||
public static readonly ValueTypeTestData<bool> BoolTestData = new ValueTypeTestData<bool>(true, false);
|
||||
|
||||
/// <summary>
|
||||
/// Common <see cref="TestData"/> for a <see cref="double"/>.
|
||||
/// </summary>
|
||||
public static readonly ValueTypeTestData<double> DoubleTestData = new ValueTypeTestData<double>(
|
||||
-1.0,
|
||||
0.0,
|
||||
1.0,
|
||||
double.MinValue,
|
||||
double.MaxValue,
|
||||
double.PositiveInfinity,
|
||||
double.NegativeInfinity);
|
||||
|
||||
/// <summary>
|
||||
/// Common <see cref="TestData"/> for a <see cref="float"/>.
|
||||
/// </summary>
|
||||
public static readonly ValueTypeTestData<float> FloatTestData = new ValueTypeTestData<float>(
|
||||
-1.0f,
|
||||
0.0f,
|
||||
1.0f,
|
||||
float.MinValue,
|
||||
float.MaxValue,
|
||||
float.PositiveInfinity,
|
||||
float.NegativeInfinity);
|
||||
|
||||
/// <summary>
|
||||
/// Common <see cref="TestData"/> for a <see cref="decimal"/>.
|
||||
/// </summary>
|
||||
public static readonly ValueTypeTestData<decimal> DecimalTestData = new ValueTypeTestData<decimal>(
|
||||
-1M,
|
||||
0M,
|
||||
1M,
|
||||
decimal.MinValue,
|
||||
decimal.MaxValue);
|
||||
|
||||
/// <summary>
|
||||
/// Common <see cref="TestData"/> for a <see cref="DateTime"/>.
|
||||
/// </summary>
|
||||
public static readonly ValueTypeTestData<DateTime> DateTimeTestData = new ValueTypeTestData<DateTime>(
|
||||
DateTime.Now,
|
||||
DateTime.UtcNow,
|
||||
DateTime.MaxValue,
|
||||
DateTime.MinValue);
|
||||
|
||||
/// <summary>
|
||||
/// Common <see cref="TestData"/> for a <see cref="TimeSpan"/>.
|
||||
/// </summary>
|
||||
public static readonly ValueTypeTestData<TimeSpan> TimeSpanTestData = new ValueTypeTestData<TimeSpan>(
|
||||
TimeSpan.MinValue,
|
||||
TimeSpan.MaxValue);
|
||||
|
||||
/// <summary>
|
||||
/// Common <see cref="TestData"/> for a <see cref="Guid"/>.
|
||||
/// </summary>
|
||||
public static readonly ValueTypeTestData<Guid> GuidTestData = new ValueTypeTestData<Guid>(
|
||||
Guid.NewGuid(),
|
||||
Guid.Empty);
|
||||
|
||||
/// <summary>
|
||||
/// Common <see cref="TestData"/> for a <see cref="DateTimeOffset"/>.
|
||||
/// </summary>
|
||||
public static readonly ValueTypeTestData<DateTimeOffset> DateTimeOffsetTestData = new ValueTypeTestData<DateTimeOffset>(
|
||||
DateTimeOffset.MaxValue,
|
||||
DateTimeOffset.MinValue,
|
||||
new DateTimeOffset(DateTime.Now));
|
||||
|
||||
/// <summary>
|
||||
/// Common <see cref="TestData"/> for an <c>enum</c>.
|
||||
/// </summary>
|
||||
public static readonly ValueTypeTestData<SimpleEnum> SimpleEnumTestData = new ValueTypeTestData<SimpleEnum>(
|
||||
SimpleEnum.First,
|
||||
SimpleEnum.Second,
|
||||
SimpleEnum.Third);
|
||||
|
||||
/// <summary>
|
||||
/// Common <see cref="TestData"/> for an <c>enum</c> implemented with a <see cref="long"/>.
|
||||
/// </summary>
|
||||
public static readonly ValueTypeTestData<LongEnum> LongEnumTestData = new ValueTypeTestData<LongEnum>(
|
||||
LongEnum.FirstLong,
|
||||
LongEnum.SecondLong,
|
||||
LongEnum.ThirdLong);
|
||||
|
||||
/// <summary>
|
||||
/// Common <see cref="TestData"/> for an <c>enum</c> decorated with a <see cref="FlagsAttribtute"/>.
|
||||
/// </summary>
|
||||
public static readonly ValueTypeTestData<FlagsEnum> FlagsEnumTestData = new ValueTypeTestData<FlagsEnum>(
|
||||
FlagsEnum.One,
|
||||
FlagsEnum.Two,
|
||||
FlagsEnum.Four);
|
||||
|
||||
/// <summary>
|
||||
/// Expected permutations of non supported file paths.
|
||||
/// </summary>
|
||||
public static readonly TestData<string> NotSupportedFilePaths = new RefTypeTestData<string>(() => new List<string>() {
|
||||
"cc:\\a\\b",
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Expected permutations of invalid file paths.
|
||||
/// </summary>
|
||||
public static readonly TestData<string> InvalidNonNullFilePaths = new RefTypeTestData<string>(() => new List<string>() {
|
||||
String.Empty,
|
||||
"",
|
||||
" ",
|
||||
" ",
|
||||
"\t\t \n ",
|
||||
"c:\\a<b",
|
||||
"c:\\a>b",
|
||||
"c:\\a\"b",
|
||||
"c:\\a\tb",
|
||||
"c:\\a|b",
|
||||
"c:\\a\bb",
|
||||
"c:\\a\0b",
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// All expected permutations of an empty string.
|
||||
/// </summary>
|
||||
public static readonly TestData<string> NonNullEmptyStrings = new RefTypeTestData<string>(() => new List<string>() { String.Empty, "", " ", "\t\r\n" });
|
||||
|
||||
/// <summary>
|
||||
/// All expected permutations of an empty string.
|
||||
/// </summary>
|
||||
public static readonly TestData<string> EmptyStrings = new RefTypeTestData<string>(() => new List<string>() { null, String.Empty, "", " ", "\t\r\n" });
|
||||
|
||||
/// <summary>
|
||||
/// Common <see cref="TestData"/> for a <see cref="string"/>.
|
||||
/// </summary>
|
||||
public static readonly RefTypeTestData<string> StringTestData = new RefTypeTestData<string>(() => new List<string>() {
|
||||
"",
|
||||
" ", // one space
|
||||
" ", // multiple spaces
|
||||
" data ", // leading and trailing whitespace
|
||||
"\t\t \n ",
|
||||
"Some String!"});
|
||||
|
||||
/// <summary>
|
||||
/// A read-only collection of value type test data.
|
||||
/// </summary>
|
||||
public static readonly ReadOnlyCollection<TestData> ValueTypeTestDataCollection = new ReadOnlyCollection<TestData>(new TestData[] {
|
||||
CharTestData,
|
||||
IntTestData,
|
||||
UintTestData,
|
||||
ShortTestData,
|
||||
UshortTestData,
|
||||
LongTestData,
|
||||
UlongTestData,
|
||||
ByteTestData,
|
||||
SByteTestData,
|
||||
BoolTestData,
|
||||
DoubleTestData,
|
||||
FloatTestData,
|
||||
DecimalTestData,
|
||||
TimeSpanTestData,
|
||||
GuidTestData,
|
||||
DateTimeOffsetTestData,
|
||||
SimpleEnumTestData,
|
||||
LongEnumTestData,
|
||||
FlagsEnumTestData});
|
||||
|
||||
/// <summary>
|
||||
/// A read-only collection of representative values and reference type test data.
|
||||
/// Uses where exhaustive coverage is not required.
|
||||
/// </summary>
|
||||
public static readonly ReadOnlyCollection<TestData> RepresentativeValueAndRefTypeTestDataCollection = new ReadOnlyCollection<TestData>(new TestData[] {
|
||||
IntTestData,
|
||||
BoolTestData,
|
||||
SimpleEnumTestData,
|
||||
StringTestData,
|
||||
});
|
||||
|
||||
private Dictionary<TestDataVariations, TestDataVariationProvider> registeredTestDataVariations;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TestData"/> class.
|
||||
/// </summary>
|
||||
/// <param name="type">The type associated with the <see cref="TestData"/> instance.</param>
|
||||
protected TestData(Type type)
|
||||
{
|
||||
this.Type = type;
|
||||
this.registeredTestDataVariations = new Dictionary<TestDataVariations, TestDataVariationProvider>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type associated with the <see cref="TestData"/> instance.
|
||||
/// </summary>
|
||||
public Type Type { get; private set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the supported test data variations.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<TestDataVariations> GetSupportedTestDataVariations()
|
||||
{
|
||||
return this.registeredTestDataVariations.Keys;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the related type for the given test data variation or returns null if the <see cref="TestData"/> instance
|
||||
/// doesn't support the given variation.
|
||||
/// </summary>
|
||||
/// <param name="variation">The test data variation with which to create the related <see cref="Type"/>.</param>
|
||||
/// <returns>The related <see cref="Type"/> for the <see cref="TestData.Type"/> as given by the test data variation.</returns>
|
||||
/// <example>
|
||||
/// For example, if the given <see cref="TestData"/> was created for <see cref="string"/> test data and the varation parameter
|
||||
/// was <see cref="TestDataVariations.AsList"/> then the returned type would be <see cref="List<string>"/>.
|
||||
/// </example>
|
||||
public Type GetAsTypeOrNull(TestDataVariations variation)
|
||||
{
|
||||
TestDataVariationProvider testDataVariation = null;
|
||||
if (this.registeredTestDataVariations.TryGetValue(variation, out testDataVariation))
|
||||
{
|
||||
return testDataVariation.Type;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets test data for the given test data variation or returns null if the <see cref="TestData"/> instance
|
||||
/// doesn't support the given variation.
|
||||
/// </summary>
|
||||
/// <param name="variation">The test data variation with which to create the related test data.</param>
|
||||
/// <returns>Test data of the type specified by the <see cref="TestData.GetAsTypeOrNull"/> method.</returns>
|
||||
public object GetAsTestDataOrNull(TestDataVariations variation)
|
||||
{
|
||||
TestDataVariationProvider testDataVariation = null;
|
||||
if (this.registeredTestDataVariations.TryGetValue(variation, out testDataVariation))
|
||||
{
|
||||
return testDataVariation.TestDataProvider();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Allows derived classes to register a <paramref name="testDataProvider "/> <see cref="Func<>"/> that will
|
||||
/// provide test data for a given variation.
|
||||
/// </summary>
|
||||
/// <param name="variation">The variation with which to register the <paramref name="testDataProvider "/>r.</param>
|
||||
/// <param name="type">The type of the test data created by the <paramref name="testDataProvider "/></param>
|
||||
/// <param name="testDataProvider">A <see cref="Func<>"/> that will provide test data.</param>
|
||||
protected void RegisterTestDataVariation(TestDataVariations variation, Type type, Func<object> testDataProvider)
|
||||
{
|
||||
this.registeredTestDataVariations.Add(variation, new TestDataVariationProvider(type, testDataProvider));
|
||||
}
|
||||
|
||||
private class TestDataVariationProvider
|
||||
{
|
||||
public TestDataVariationProvider(Type type, Func<object> testDataProvider)
|
||||
{
|
||||
this.Type = type;
|
||||
this.TestDataProvider = testDataProvider;
|
||||
}
|
||||
|
||||
|
||||
public Func<object> TestDataProvider { get; private set; }
|
||||
|
||||
public Type Type { get; private set; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A generic base class for test data.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type associated with the test data.</typeparam>
|
||||
public abstract class TestData<T> : TestData, IEnumerable<T>
|
||||
{
|
||||
private static readonly Type OpenIEnumerableType = typeof(IEnumerable<>);
|
||||
private static readonly Type OpenListType = typeof(List<>);
|
||||
private static readonly Type OpenIQueryableType = typeof(IQueryable<>);
|
||||
private static readonly Type OpenDictionaryType = typeof(Dictionary<,>);
|
||||
private static readonly Type OpenTestDataHolderType = typeof(TestDataHolder<>);
|
||||
private int dictionaryKey;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TestData<T>"/> class.
|
||||
/// </summary>
|
||||
protected TestData()
|
||||
: base(typeof(T))
|
||||
{
|
||||
Type[] typeParams = new Type[] { this.Type };
|
||||
Type[] dictionaryTypeParams = new Type[] { typeof(string), this.Type };
|
||||
|
||||
Type arrayType = this.Type.MakeArrayType();
|
||||
Type listType = OpenListType.MakeGenericType(typeParams);
|
||||
Type iEnumerableType = OpenIEnumerableType.MakeGenericType(typeParams);
|
||||
Type iQueryableType = OpenIQueryableType.MakeGenericType(typeParams);
|
||||
Type dictionaryType = OpenDictionaryType.MakeGenericType(dictionaryTypeParams);
|
||||
Type testDataHolderType = OpenTestDataHolderType.MakeGenericType(typeParams);
|
||||
|
||||
this.RegisterTestDataVariation(TestDataVariations.AsInstance, this.Type, () => GetTypedTestData());
|
||||
this.RegisterTestDataVariation(TestDataVariations.AsArray, arrayType, GetTestDataAsArray);
|
||||
this.RegisterTestDataVariation(TestDataVariations.AsIEnumerable, iEnumerableType, GetTestDataAsIEnumerable);
|
||||
this.RegisterTestDataVariation(TestDataVariations.AsIQueryable, iQueryableType, GetTestDataAsIQueryable);
|
||||
this.RegisterTestDataVariation(TestDataVariations.AsList, listType, GetTestDataAsList);
|
||||
this.RegisterTestDataVariation(TestDataVariations.AsDictionary, dictionaryType, GetTestDataAsDictionary);
|
||||
this.RegisterTestDataVariation(TestDataVariations.AsClassMember, testDataHolderType, GetTestDataInHolder);
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
return (IEnumerator<T>)this.GetTypedTestData().ToList().GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return (IEnumerator)this.GetTypedTestData().ToList().GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the test data as an array.
|
||||
/// </summary>
|
||||
/// <returns>An array of test data of the given type.</returns>
|
||||
public T[] GetTestDataAsArray()
|
||||
{
|
||||
return this.GetTypedTestData().ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the test data as a <see cref="List<>"/>.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="List<>"/> of test data of the given type.</returns>
|
||||
public List<T> GetTestDataAsList()
|
||||
{
|
||||
return this.GetTypedTestData().ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the test data as an <see cref="IEnumerable<>"/>.
|
||||
/// </summary>
|
||||
/// <returns>An <see cref="IEnumerable<>"/> of test data of the given type.</returns>
|
||||
public IEnumerable<T> GetTestDataAsIEnumerable()
|
||||
{
|
||||
return this.GetTypedTestData().AsEnumerable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the test data as an <see cref="IQueryable<>"/>.
|
||||
/// </summary>
|
||||
/// <returns>An <see cref="IQueryable<>"/> of test data of the given type.</returns>
|
||||
public IQueryable<T> GetTestDataAsIQueryable()
|
||||
{
|
||||
//return this.GetTypedTestData().AsQueryable();
|
||||
return null;
|
||||
}
|
||||
|
||||
public Dictionary<string, T> GetTestDataAsDictionary()
|
||||
{
|
||||
// Some TestData collections contain duplicates e.g. UintTestData contains both 0 and UInt32.MinValue.
|
||||
// Therefore use dictionaryKey, not _unused.ToString(). Reset key to keep dictionaries consistent if used
|
||||
// multiple times.
|
||||
dictionaryKey = 0;
|
||||
return this.GetTypedTestData().ToDictionary(_unused => (dictionaryKey++).ToString());
|
||||
}
|
||||
|
||||
public IEnumerable<TestDataHolder<T>> GetTestDataInHolder()
|
||||
{
|
||||
return this.GetTypedTestData().Select(value => new TestDataHolder<T> { V1 = value, });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Must be implemented by derived types to return test data of the given type.
|
||||
/// </summary>
|
||||
/// <returns>Test data of the given type.</returns>
|
||||
protected abstract IEnumerable<T> GetTypedTestData();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Microsoft.TestCommon
|
||||
{
|
||||
/// <summary>
|
||||
/// Equatable class wrapping a single instance of type <paramref name="T"/>. Equatable to ease test assertions.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The <see cref="Type"/> to wrap.</typeparam>
|
||||
public class TestDataHolder<T> : IEquatable<TestDataHolder<T>>
|
||||
{
|
||||
public T V1 { get; set; }
|
||||
|
||||
bool IEquatable<TestDataHolder<T>>.Equals(TestDataHolder<T> other)
|
||||
{
|
||||
if (other == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Object.Equals(V1, other.V1);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
TestDataHolder<T> that = obj as TestDataHolder<T>;
|
||||
return ((IEquatable<TestDataHolder<T>>)this).Equals(that);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
if (typeof(ValueType).GetTypeInfo().IsAssignableFrom(typeof(T).GetTypeInfo()) || V1 != null)
|
||||
{
|
||||
return V1.GetHashCode();
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return String.Format(CultureInfo.InvariantCulture, "{{ V1: '{0}' }}", V1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,205 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Xunit.Sdk;
|
||||
|
||||
namespace Microsoft.TestCommon
|
||||
{
|
||||
public class TestDataSetAttribute : DataAttribute
|
||||
{
|
||||
public Type DeclaringType { get; set; }
|
||||
|
||||
public string PropertyName { get; set; }
|
||||
|
||||
public TestDataVariations TestDataVariations { get; set; }
|
||||
|
||||
private IEnumerable<Tuple<Type, string>> ExtraDataSets { get; set; }
|
||||
|
||||
public TestDataSetAttribute(Type declaringType, string propertyName, TestDataVariations testDataVariations = TestCommon.TestDataVariations.All)
|
||||
{
|
||||
DeclaringType = declaringType;
|
||||
PropertyName = propertyName;
|
||||
TestDataVariations = testDataVariations;
|
||||
ExtraDataSets = new List<Tuple<Type, string>>();
|
||||
}
|
||||
|
||||
public TestDataSetAttribute(Type declaringType, string propertyName,
|
||||
Type declaringType1, string propertyName1,
|
||||
TestDataVariations testDataVariations = TestCommon.TestDataVariations.All)
|
||||
: this(declaringType, propertyName, testDataVariations)
|
||||
{
|
||||
ExtraDataSets = new List<Tuple<Type, string>> { Tuple.Create(declaringType1, propertyName1) };
|
||||
}
|
||||
|
||||
public TestDataSetAttribute(Type declaringType, string propertyName,
|
||||
Type declaringType1, string propertyName1,
|
||||
Type declaringType2, string propertyName2,
|
||||
TestDataVariations testDataVariations = TestCommon.TestDataVariations.All)
|
||||
: this(declaringType, propertyName, testDataVariations)
|
||||
{
|
||||
ExtraDataSets = new List<Tuple<Type, string>> { Tuple.Create(declaringType1, propertyName1), Tuple.Create(declaringType2, propertyName2) };
|
||||
}
|
||||
|
||||
public TestDataSetAttribute(Type declaringType, string propertyName,
|
||||
Type declaringType1, string propertyName1,
|
||||
Type declaringType2, string propertyName2,
|
||||
Type declaringType3, string propertyName3,
|
||||
TestDataVariations testDataVariations = TestCommon.TestDataVariations.All)
|
||||
: this(declaringType, propertyName, testDataVariations)
|
||||
{
|
||||
ExtraDataSets = new List<Tuple<Type, string>> { Tuple.Create(declaringType1, propertyName1), Tuple.Create(declaringType2, propertyName2), Tuple.Create(declaringType3, propertyName3) };
|
||||
}
|
||||
|
||||
public TestDataSetAttribute(Type declaringType, string propertyName,
|
||||
Type declaringType1, string propertyName1,
|
||||
Type declaringType2, string propertyName2,
|
||||
Type declaringType3, string propertyName3,
|
||||
Type declaringType4, string propertyName4,
|
||||
TestDataVariations testDataVariations = TestCommon.TestDataVariations.All)
|
||||
: this(declaringType, propertyName, testDataVariations)
|
||||
{
|
||||
ExtraDataSets = new List<Tuple<Type, string>>
|
||||
{
|
||||
Tuple.Create(declaringType1, propertyName1), Tuple.Create(declaringType2, propertyName2),
|
||||
Tuple.Create(declaringType3, propertyName3), Tuple.Create(declaringType4, propertyName4)
|
||||
};
|
||||
}
|
||||
|
||||
public override IEnumerable<object[]> GetData(MethodInfo testMethod)
|
||||
{
|
||||
IEnumerable<object[]> baseDataSet = GetBaseDataSet(DeclaringType, PropertyName, TestDataVariations);
|
||||
IEnumerable<IEnumerable<object[]>> extraDataSets = GetExtraDataSets();
|
||||
|
||||
IEnumerable<IEnumerable<object[]>> finalDataSets = (new[] { baseDataSet }).Concat(extraDataSets);
|
||||
|
||||
var datasets = CrossProduct(finalDataSets);
|
||||
|
||||
return datasets;
|
||||
}
|
||||
|
||||
private static IEnumerable<object[]> CrossProduct(IEnumerable<IEnumerable<object[]>> datasets)
|
||||
{
|
||||
if (datasets.Count() == 1)
|
||||
{
|
||||
foreach (var dataset in datasets.First())
|
||||
{
|
||||
yield return dataset;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
IEnumerable<object[]> datasetLeft = datasets.First();
|
||||
IEnumerable<object[]> datasetRight = CrossProduct(datasets.Skip(1));
|
||||
|
||||
foreach (var dataLeft in datasetLeft)
|
||||
{
|
||||
foreach (var dataRight in datasetRight)
|
||||
{
|
||||
yield return dataLeft.Concat(dataRight).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The base data set(first one) can either be a TestDataSet or a TestDataSetCollection
|
||||
private static IEnumerable<object[]> GetBaseDataSet(Type declaringType, string propertyName, TestDataVariations variations)
|
||||
{
|
||||
return TryGetDataSetFromTestDataCollection(declaringType, propertyName, variations) ?? GetDataSet(declaringType, propertyName);
|
||||
}
|
||||
|
||||
private IEnumerable<IEnumerable<object[]>> GetExtraDataSets()
|
||||
{
|
||||
foreach (var tuple in ExtraDataSets)
|
||||
{
|
||||
yield return GetDataSet(tuple.Item1, tuple.Item2);
|
||||
}
|
||||
}
|
||||
|
||||
private static object GetTestDataPropertyValue(Type declaringType, string propertyName)
|
||||
{
|
||||
PropertyInfo property = declaringType.GetProperty(propertyName, BindingFlags.Static | BindingFlags.Public);
|
||||
|
||||
if (property == null)
|
||||
{
|
||||
throw new ArgumentException(String.Format("Could not find public static property {0} on {1}", propertyName, declaringType.FullName));
|
||||
}
|
||||
else
|
||||
{
|
||||
return property.GetValue(null, null);
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<object[]> GetDataSet(Type declaringType, string propertyName)
|
||||
{
|
||||
object propertyValue = GetTestDataPropertyValue(declaringType, propertyName);
|
||||
|
||||
// box the dataset items if the property is not a RefTypeTestData
|
||||
IEnumerable<object> value = (propertyValue as IEnumerable<object>) ?? (propertyValue as IEnumerable).Cast<object>();
|
||||
if (value == null)
|
||||
{
|
||||
throw new InvalidOperationException(String.Format("{0}.{1} is either null or does not implement IEnumerable", declaringType.FullName, propertyName));
|
||||
}
|
||||
|
||||
IEnumerable<object[]> dataset = value as IEnumerable<object[]>;
|
||||
if (dataset != null)
|
||||
{
|
||||
return dataset;
|
||||
}
|
||||
else
|
||||
{
|
||||
return value.Select((data) => new object[] { data });
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<object[]> TryGetDataSetFromTestDataCollection(Type declaringType, string propertyName, TestDataVariations variations)
|
||||
{
|
||||
object propertyValue = GetTestDataPropertyValue(declaringType, propertyName);
|
||||
|
||||
IEnumerable<TestData> testDataCollection = propertyValue as IEnumerable<TestData>;
|
||||
|
||||
return testDataCollection == null ? null : GetDataSetFromTestDataCollection(testDataCollection, variations);
|
||||
}
|
||||
|
||||
private static IEnumerable<object[]> GetDataSetFromTestDataCollection(IEnumerable<TestData> testDataCollection, TestDataVariations variations)
|
||||
{
|
||||
foreach (TestData testdataInstance in testDataCollection)
|
||||
{
|
||||
foreach (TestDataVariations variation in testdataInstance.GetSupportedTestDataVariations())
|
||||
{
|
||||
if ((variation & variations) == variation)
|
||||
{
|
||||
Type variationType = testdataInstance.GetAsTypeOrNull(variation);
|
||||
object testData = testdataInstance.GetAsTestDataOrNull(variation);
|
||||
if (AsSingleInstances(variation))
|
||||
{
|
||||
foreach (object obj in (IEnumerable)testData)
|
||||
{
|
||||
yield return new object[] { variationType, obj };
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return new object[] { variationType, testData };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool AsSingleInstances(TestDataVariations variation)
|
||||
{
|
||||
return variation == TestDataVariations.AsInstance ||
|
||||
variation == TestDataVariations.AsNullable ||
|
||||
variation == TestDataVariations.AsDerivedType ||
|
||||
variation == TestDataVariations.AsKnownType ||
|
||||
variation == TestDataVariations.AsDataMember ||
|
||||
variation == TestDataVariations.AsClassMember ||
|
||||
variation == TestDataVariations.AsXmlElementProperty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.TestCommon
|
||||
{
|
||||
/// <summary>
|
||||
/// An flags enum that can be used to indicate different variations of a given
|
||||
/// <see cref="TestData"/> instance.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum TestDataVariations
|
||||
{
|
||||
/// <summary>
|
||||
/// An individual instance of a given <see cref="TestData"/> type.
|
||||
/// </summary>
|
||||
AsInstance = 0x1,
|
||||
|
||||
/// <summary>
|
||||
/// An individual instance of a type that derives from a given <see cref="TestData"/> type.
|
||||
/// </summary>
|
||||
AsDerivedType = 0x2,
|
||||
|
||||
/// <summary>
|
||||
/// An individual instance of a given <see cref="TestData"/> type that has a property value
|
||||
/// that is a known type of the declared property type.
|
||||
/// </summary>
|
||||
AsKnownType = 0x4,
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="Nullable<>"/> instance of a given <see cref="TestData"/> type. Only applies to
|
||||
/// instances of <see cref="ValueTypeTestData"/>.
|
||||
/// </summary>
|
||||
AsNullable = 0x8,
|
||||
|
||||
/// <summary>
|
||||
/// An instance of a <see cref="System.Collections.Generic.List<>"/> of a given <see cref="TestData"/> type.
|
||||
/// </summary>
|
||||
AsList = 0x10,
|
||||
|
||||
/// <summary>
|
||||
/// An instance of a array of the <see cref="TestData"/> type.
|
||||
/// </summary>
|
||||
AsArray = 0x20,
|
||||
|
||||
/// <summary>
|
||||
/// An instance of an <see cref="System.Collections.Generic.IEnumerable<>"/> of a given <see cref="TestData"/> type.
|
||||
/// </summary>
|
||||
AsIEnumerable = 0x40,
|
||||
|
||||
/// <summary>
|
||||
/// An instance of an <see cref="System.Linq.IQueryable<>"/> of a given <see cref="TestData"/> type.
|
||||
/// </summary>
|
||||
AsIQueryable = 0x80,
|
||||
|
||||
/// <summary>
|
||||
/// An instance of a DataContract type in which a given <see cref="TestData"/> type is a member.
|
||||
/// </summary>
|
||||
AsDataMember = 0x100,
|
||||
|
||||
/// <summary>
|
||||
/// An instance of a type in which a given <see cref="TestData"/> type is decorated with a
|
||||
/// <see cref="System.Xml.Serialization.XmlElementAttribute"/>.
|
||||
/// </summary>
|
||||
AsXmlElementProperty = 0x200,
|
||||
|
||||
/// <summary>
|
||||
/// An instance of a <see cref="System.Collections.Generic.IDictionary{string,TValue}"/> of a given
|
||||
/// <see cref="TestData"/> type.
|
||||
/// </summary>
|
||||
AsDictionary = 0x400,
|
||||
|
||||
/// <summary>
|
||||
/// Add a <c>null</c> instance of the given <see cref="TestData"/> type to the data set. This variation is
|
||||
/// not included in <see cref="All"/> or other variation masks.
|
||||
/// </summary>
|
||||
WithNull = 0x800,
|
||||
|
||||
/// <summary>
|
||||
/// Individual instances of <see cref="TestDataHolder{T}"/> containing the given <see cref="TestData"/>. This
|
||||
/// variation is not included in <see cref="All"/> or other variation masks.
|
||||
/// </summary>
|
||||
AsClassMember = 0x1000,
|
||||
|
||||
/// <summary>
|
||||
/// All of the flags for single instance variations of a given <see cref="TestData"/> type.
|
||||
/// </summary>
|
||||
AllSingleInstances = AsInstance | AsDerivedType | AsKnownType | AsNullable,
|
||||
|
||||
/// <summary>
|
||||
/// All of the flags for collection variations of a given <see cref="TestData"/> type.
|
||||
/// </summary>
|
||||
AllCollections = AsList | AsArray | AsIEnumerable | AsIQueryable | AsDictionary,
|
||||
|
||||
/// <summary>
|
||||
/// All of the flags for variations in which a given <see cref="TestData"/> type is a property on another type.
|
||||
/// </summary>
|
||||
AllProperties = AsDataMember | AsXmlElementProperty,
|
||||
|
||||
/// <summary>
|
||||
/// All of the flags for interface collection variations of a given <see cref="TestData"/> type.
|
||||
/// </summary>
|
||||
AllInterfaces = AsIEnumerable | AsIQueryable,
|
||||
|
||||
/// <summary>
|
||||
/// All of the flags except for the interface collection variations of a given <see cref="TestData"/> type.
|
||||
/// </summary>
|
||||
AllNonInterfaces = All & ~AllInterfaces,
|
||||
|
||||
/// <summary>
|
||||
/// All of the flags for all of the variations of a given <see cref="TestData"/> type.
|
||||
/// </summary>
|
||||
All = AllSingleInstances | AllCollections | AllProperties
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,165 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.TestCommon
|
||||
{
|
||||
/// <summary>
|
||||
/// MSTest utility for testing that a given type has the expected properties such as being public, sealed, etc.
|
||||
/// </summary>
|
||||
public class TypeAssert
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies a set of type properties to test for using the <see cref="CheckProperty"/> method.
|
||||
/// This enumeration has a <see cref="FlagsAttribute"/> attribute that allows a bitwise combination of its member values.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum TypeProperties
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that the type must be abstract.
|
||||
/// </summary>
|
||||
IsAbstract = 0x1,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the type must be a class.
|
||||
/// </summary>
|
||||
IsClass = 0x2,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the type must be a COM object.
|
||||
/// </summary>
|
||||
IsComObject = 0x4,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the type must be disposable.
|
||||
/// </summary>
|
||||
IsDisposable = 0x8,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the type must be an enum.
|
||||
/// </summary>
|
||||
IsEnum = 0x10,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the type must be a generic type.
|
||||
/// </summary>
|
||||
IsGenericType = 0x20,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the type must be a generic type definition.
|
||||
/// </summary>
|
||||
IsGenericTypeDefinition = 0x40,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the type must be an interface.
|
||||
/// </summary>
|
||||
IsInterface = 0x80,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the type must be nested and declared private.
|
||||
/// </summary>
|
||||
IsNestedPrivate = 0x100,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the type must be nested and declared public.
|
||||
/// </summary>
|
||||
IsNestedPublic = 0x200,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the type must be public.
|
||||
/// </summary>
|
||||
IsPublic = 0x400,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the type must be sealed.
|
||||
/// </summary>
|
||||
IsSealed = 0x800,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the type must be visible outside the assembly.
|
||||
/// </summary>
|
||||
IsVisible = 0x1000,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the type must be static.
|
||||
/// </summary>
|
||||
IsStatic = TypeAssert.TypeProperties.IsAbstract | TypeAssert.TypeProperties.IsSealed,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the type must be a public, visible class.
|
||||
/// </summary>
|
||||
IsPublicVisibleClass = TypeAssert.TypeProperties.IsClass | TypeAssert.TypeProperties.IsPublic | TypeAssert.TypeProperties.IsVisible
|
||||
}
|
||||
|
||||
private static void CheckProperty(Type type, bool expected, bool actual, string property)
|
||||
{
|
||||
Assert.NotNull(type);
|
||||
Assert.True(expected == actual, String.Format("Type '{0}' should{1} be {2}.", type.FullName, expected ? "" : " NOT", property));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified type has a given set of properties such as being public, sealed, etc.
|
||||
/// The method asserts if one or more of the properties are not satisfied.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type to test for properties.</typeparam>
|
||||
/// <param name="typeProperties">The set of type properties to test for.</param>
|
||||
public void HasProperties<T>(TypeProperties typeProperties)
|
||||
{
|
||||
HasProperties(typeof(T), typeProperties);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified type has a given set of properties such as being public, sealed, etc.
|
||||
/// The method asserts if one or more of the properties are not satisfied.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type to test for properties.</typeparam>
|
||||
/// <typeparam name="TIsAssignableFrom">Verify that the type to test is assignable from this type.</typeparam>
|
||||
/// <param name="typeProperties">The set of type properties to test for.</param>
|
||||
public void HasProperties<T, TIsAssignableFrom>(TypeProperties typeProperties)
|
||||
{
|
||||
HasProperties(typeof(T), typeProperties, typeof(TIsAssignableFrom));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified type has a given set of properties such as being public, sealed, etc.
|
||||
/// The method asserts if one or more of the properties are not satisfied.
|
||||
/// </summary>
|
||||
/// <param name="type">The type to test for properties.</param>
|
||||
/// <param name="typeProperties">The set of type properties to test for.</param>
|
||||
public void HasProperties(Type type, TypeProperties typeProperties)
|
||||
{
|
||||
HasProperties(type, typeProperties, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified type has a given set of properties such as being public, sealed, etc.
|
||||
/// The method asserts if one or more of the properties are not satisfied.
|
||||
/// </summary>
|
||||
/// <param name="type">The type to test for properties.</param>
|
||||
/// <param name="typeProperties">The set of type properties to test for.</param>
|
||||
/// <param name="isAssignableFrom">Verify that the type to test is assignable from this type.</param>
|
||||
public void HasProperties(Type type, TypeProperties typeProperties, Type isAssignableFrom)
|
||||
{
|
||||
TypeAssert.CheckProperty(type, (typeProperties & TypeProperties.IsAbstract) > 0, type.GetTypeInfo().IsAbstract, "abstract");
|
||||
TypeAssert.CheckProperty(type, (typeProperties & TypeProperties.IsClass) > 0, type.GetTypeInfo().IsClass, "a class");
|
||||
TypeAssert.CheckProperty(type, (typeProperties & TypeProperties.IsDisposable) > 0, typeof(IDisposable).IsAssignableFrom(type), "disposable");
|
||||
TypeAssert.CheckProperty(type, (typeProperties & TypeProperties.IsEnum) > 0, type.GetTypeInfo().IsEnum, "an enum");
|
||||
TypeAssert.CheckProperty(type, (typeProperties & TypeProperties.IsGenericType) > 0, type.GetTypeInfo().IsGenericType, "a generic type");
|
||||
TypeAssert.CheckProperty(type, (typeProperties & TypeProperties.IsGenericTypeDefinition) > 0, type.GetTypeInfo().IsGenericTypeDefinition, "a generic type definition");
|
||||
TypeAssert.CheckProperty(type, (typeProperties & TypeProperties.IsInterface) > 0, type.GetTypeInfo().IsInterface, "an interface");
|
||||
TypeAssert.CheckProperty(type, (typeProperties & TypeProperties.IsNestedPrivate) > 0, type.GetTypeInfo().IsNestedPrivate, "nested private");
|
||||
TypeAssert.CheckProperty(type, (typeProperties & TypeProperties.IsNestedPublic) > 0, type.GetTypeInfo().IsNestedPublic, "nested public");
|
||||
TypeAssert.CheckProperty(type, (typeProperties & TypeProperties.IsPublic) > 0, type.GetTypeInfo().IsPublic, "public");
|
||||
TypeAssert.CheckProperty(type, (typeProperties & TypeProperties.IsSealed) > 0, type.GetTypeInfo().IsSealed, "sealed");
|
||||
TypeAssert.CheckProperty(type, (typeProperties & TypeProperties.IsVisible) > 0, type.GetTypeInfo().IsVisible, "visible");
|
||||
if (isAssignableFrom != null)
|
||||
{
|
||||
TypeAssert.CheckProperty(type, true, isAssignableFrom.GetTypeInfo().IsAssignableFrom(type.GetTypeInfo()), String.Format("assignable from {0}", isAssignableFrom.FullName));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.TestCommon
|
||||
{
|
||||
public class ValueTypeTestData<T> : TestData<T> where T : struct
|
||||
{
|
||||
private static readonly Type OpenNullableType = typeof(Nullable<>);
|
||||
private T[] testData;
|
||||
|
||||
public ValueTypeTestData(params T[] testData)
|
||||
: base()
|
||||
{
|
||||
this.testData = testData;
|
||||
|
||||
Type[] typeParams = new Type[] { this.Type };
|
||||
this.RegisterTestDataVariation(TestDataVariations.WithNull, OpenNullableType.MakeGenericType(typeParams), GetNullTestData);
|
||||
this.RegisterTestDataVariation(TestDataVariations.AsNullable, OpenNullableType.MakeGenericType(typeParams), GetTestDataAsNullable);
|
||||
}
|
||||
|
||||
public object GetNullTestData()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public IEnumerable<Nullable<T>> GetTestDataAsNullable()
|
||||
{
|
||||
return this.GetTypedTestData().Select(d => new Nullable<T>(d));
|
||||
}
|
||||
|
||||
protected override IEnumerable<T> GetTypedTestData()
|
||||
{
|
||||
return this.testData;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"compilationOptions": {
|
||||
"warningsAsErrors": "true"
|
||||
"warningsAsErrors": false
|
||||
},
|
||||
"dependencies": {
|
||||
"Microsoft.AspNet.Mvc": "6.0.0-*",
|
||||
|
|
@ -13,6 +13,15 @@
|
|||
"test": "Xunit.KRunner"
|
||||
},
|
||||
"frameworks": {
|
||||
"aspnet50": { }
|
||||
"aspnet50": {
|
||||
"frameworkAssemblies": {
|
||||
"System.Net.Http": "4.0.0.0"
|
||||
}
|
||||
},
|
||||
"aspnetcore50": {
|
||||
"dependencies": {
|
||||
"System.Net.Http": "4.0.0-beta-*"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
"Microsoft.AspNet.Server.IIS": "1.0.0-*"
|
||||
},
|
||||
"frameworks": {
|
||||
"aspnet50": { }
|
||||
"aspnet50": { },
|
||||
"aspnetcore50": { }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue