diff --git a/Microsoft.AspNet.Mvc/ActionResults/NoContentResult.cs b/Microsoft.AspNet.Mvc/ActionResults/NoContentResult.cs new file mode 100644 index 0000000000..4ba37f566e --- /dev/null +++ b/Microsoft.AspNet.Mvc/ActionResults/NoContentResult.cs @@ -0,0 +1,27 @@ +using System; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Owin; +using System.Net; + +namespace Microsoft.AspNet.Mvc +{ + public class NoContentResult : IActionResult + { + public async Task ExecuteResultAsync(RequestContext context) + { + if (context == null) + { + throw new ArgumentNullException("context"); + } + + IOwinResponse response = context.HttpContext.Response; + + response.StatusCode = (int)HttpStatusCode.NoContent; + + await Task.FromResult(false); + + return; + } + } +} diff --git a/Microsoft.AspNet.Mvc/ActionResults/ObjectContent.cs b/Microsoft.AspNet.Mvc/ActionResults/ObjectContent.cs new file mode 100644 index 0000000000..931b7b7c3f --- /dev/null +++ b/Microsoft.AspNet.Mvc/ActionResults/ObjectContent.cs @@ -0,0 +1,159 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Diagnostics.Contracts; +using System.IO; +using System.Net.Http; +using System.Net.Http.Formatting; +using System.Net.Http.Headers; +using System.Threading.Tasks; +using System.Web.Http; + +namespace Microsoft.AspNet.Mvc +{ + /// + /// Contains a value as well as an associated that will be + /// used to serialize the value when writing this content. + /// + public class ObjectContent : HttpContent + { + private object _value; + private readonly MediaTypeFormatter _formatter; + + /// + /// Initializes a new instance of the class. + /// + /// The type of object this instance will contain. + /// The value of the object this instance will contain. + /// The formatter to use when serializing the value. + public ObjectContent(Type type, object value, MediaTypeFormatter formatter) + : this(type, value, formatter, (MediaTypeHeaderValue)null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The type of object this instance will contain. + /// The value of the object this instance will contain. + /// The formatter to use when serializing the value. + /// The authoritative value of the content's Content-Type header. Can be null in which case the + /// formatter's default content type will be used. + public ObjectContent(Type type, object value, MediaTypeFormatter formatter, string mediaType) + : this(type, value, formatter, BuildHeaderValue(mediaType)) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The type of object this instance will contain. + /// The value of the object this instance will contain. + /// The formatter to use when serializing the value. + /// The authoritative value of the content's Content-Type header. Can be null in which case the + /// formatter's default content type will be used. + public ObjectContent(Type type, object value, MediaTypeFormatter formatter, MediaTypeHeaderValue mediaType) + { + if (type == null) + { + throw new ArgumentNullException("type"); + } + if (formatter == null) + { + throw new ArgumentNullException("formatter"); + } + + if (!formatter.CanWriteType(type)) + { + throw new ArgumentNullException(formatter.GetType().FullName + " cannot write " + type.Name); + } + + _formatter = formatter; + ObjectType = type; + + VerifyAndSetObject(value); + _formatter.SetDefaultContentHeaders(type, Headers, mediaType); + } + + /// + /// Gets the type of object managed by this instance. + /// + public Type ObjectType { get; private set; } + + /// + /// The formatter associated with this content instance. + /// + public MediaTypeFormatter Formatter + { + get { return _formatter; } + } + + /// + /// Gets or sets the value of the current . + /// + public object Value + { + get { return _value; } + set { _value = value; } + } + + internal static MediaTypeHeaderValue BuildHeaderValue(string mediaType) + { + return mediaType != null ? new MediaTypeHeaderValue(mediaType) : null; + } + + /// + /// Asynchronously serializes the object's content to the given . + /// + /// The to which to write. + /// The associated . + /// A instance that is asynchronously serializing the object's content. + protected override Task SerializeToStreamAsync(Stream stream, System.Net.TransportContext context) + { + return _formatter.WriteToStreamAsync(ObjectType, Value, stream, this, context); + } + + /// + /// Computes the length of the stream if possible. + /// + /// The computed length of the stream. + /// true if the length has been computed; otherwise false. + protected override bool TryComputeLength(out long length) + { + length = -1; + return false; + } + + private static bool IsTypeNullable(Type type) + { + return !type.IsValueType() || + (type.IsGenericType() && + type.GetGenericTypeDefinition() == typeof(Nullable<>)); + } + + private void VerifyAndSetObject(object value) + { + Contract.Assert(ObjectType != null, "Type cannot be null"); + + if (value == null) + { + // Null may not be assigned to value types (unless Nullable) + if (!IsTypeNullable(ObjectType)) + { + throw new InvalidOperationException("CannotUseNullValueType " + typeof(ObjectContent).Name + " " + ObjectType.Name); + } + } + else + { + // Non-null objects must be a type assignable to Type + Type objectType = value.GetType(); + if (!ObjectType.IsAssignableFrom(objectType)) + { + throw new ArgumentException("value Resources.ObjectAndTypeDisagree, objectType.Name, ObjectType.Name"); + } + } + + _value = value; + } + } +} diff --git a/Microsoft.AspNet.Mvc/DefaultContentNegotiator.cs b/Microsoft.AspNet.Mvc/DefaultContentNegotiator.cs new file mode 100644 index 0000000000..ae70031539 --- /dev/null +++ b/Microsoft.AspNet.Mvc/DefaultContentNegotiator.cs @@ -0,0 +1,599 @@ +//// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using Microsoft.Owin; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Formatting; +using System.Net.Http.Headers; +using System.Text; +using System.Web.Http; + +namespace Microsoft.AspNet.Mvc +{ + /// + /// Class that selects a for an + /// or . + /// + public class DefaultContentNegotiator : IOwinContentNegotiator + { +// public DefaultContentNegotiator() +// : this(false) +// { +// } + +// /// +// /// Initializes a new instance of the with +// /// the given setting for . +// /// +// /// +// /// 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. +// /// +// public DefaultContentNegotiator(bool excludeMatchOnTypeOnly) +// { +// ExcludeMatchOnTypeOnly = 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. +// /// +// public bool ExcludeMatchOnTypeOnly { get; private set; } + +// /// +// /// Performs content negotiating by selecting the most appropriate out of the passed in +// /// for the given that can serialize an object of the given +// /// . +// /// +// /// The type to be serialized. +// /// The request. +// /// The set of objects from which to choose. +// /// The result of the negotiation containing the most appropriate instance, +// /// or null if there is no appropriate formatter. +// public virtual ContentNegotiationResult Negotiate(Type type, IOwinContext context, IEnumerable formatters) +// { +// // Performance-sensitive +// if (type == null) +// { +// throw new ArgumentNullException("type"); +// } +// if (context == null) +// { +// throw new ArgumentNullException("context"); +// } +// if (formatters == null) +// { +// throw new ArgumentNullException("formatters"); +// } + +// var request = context.Request; + +// // Go through each formatter to compute how well it matches. +// Collection 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; // this is OData only scenario at the moment: .GetPerRequestFormatterInstance(type, request, bestMediaType); +// return new ContentNegotiationResult(bestFormatter, bestMediaType); +// } + +// return null; +// } + +// /// +// /// Determine how well each formatter matches by associating a 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. +// /// +// /// The type to be serialized. +// /// The request. +// /// The set of objects from which to choose. +// /// A collection containing all the matches. +// protected virtual Collection ComputeFormatterMatches(Type type, IOwinRequest request, IEnumerable formatters) +// { +// // Performance-sensitive +// if (type == null) +// { +// throw new ArgumentNullException("type"); +// } +// if (request == null) +// { +// throw new ArgumentNullException("request"); +// } +// if (formatters == null) +// { +// throw new ArgumentNullException("formatters"); +// } + +// IEnumerable sortedAcceptValues = null; + +// // Go through each formatter to find how well it matches. +// List matches = new List(); +// 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 media type mapping. +// if ((match = MatchMediaTypeMapping(request, formatter)) != null) +// { +// matches.Add(match); +// 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; +// } + +// /// +// /// Select the best match among the candidate matches found. +// /// +// /// The collection of matches. +// /// The determined to be the best match. +// protected virtual MediaTypeFormatterMatch SelectResponseMediaTypeFormatter(ICollection matches) +// { +// // Performance-sensitive +// if (matches == null) +// { +// throw new ArgumentNullException("matches"); +// } + +// List 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.MatchOnRequestWithMediaTypeMapping: +// // Matches on accept headers using mappings must choose the highest quality match +// bestMatchOnMediaTypeMapping = UpdateBestMatch(bestMatchOnMediaTypeMapping, 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; +// } + +// /// +// /// 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. +// /// +// /// The determined to be the best match. +// protected virtual Encoding SelectResponseCharacterEncoding(IOwinRequest request, MediaTypeFormatter formatter) +// { +// if (request == null) +// { +// throw new ArgumentNullException("request"); +// } +// if (formatter == null) +// { +// throw new ArgumentNullException("formatter"); +// } + +// // If there are any SupportedEncodings then we pick an encoding +// List supportedEncodings = formatter.SupportedEncodingsInternal; +// if (supportedEncodings.Count > 0) +// { +// // Sort Accept-Charset header values +// IEnumerable 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 + +// // TODO: Transform to use request content headers directly +// // was - request.Content != null ? request.Content.Headers : null +// return formatter.SelectCharacterEncoding(null); +// } + +// return null; +// } + +// /// +// /// Match a request against the s registered with the formatter. +// /// +// /// The request to match. +// /// The formatter to match against. +// /// A indicating the quality of the match or null is no match. +// protected virtual MediaTypeFormatterMatch MatchMediaTypeMapping(IOwinRequest request, MediaTypeFormatter formatter) +// { +// if (request == null) +// { +// throw new ArgumentNullException("request"); +// } +// if (formatter == null) +// { +// throw new ArgumentNullException("formatter"); +// } + +// List mediaTypeMappings = formatter.MediaTypeMappingsInternal; +// for (int i = 0; i < mediaTypeMappings.Count; i++) +// { +// MediaTypeMapping mapping = mediaTypeMappings[i]; +// double quality; +// if (mapping != null && ((quality = mapping.TryMatchMediaType(request)) > FormattingUtilities.NoMatch)) +// { +// return new MediaTypeFormatterMatch(formatter, mapping.MediaType, quality, MediaTypeFormatterMatchRanking.MatchOnRequestWithMediaTypeMapping); +// } +// } + +// return null; +// } + +// /// +// /// Match the request accept header field values against the formatter's registered supported media types. +// /// +// /// The sorted accept header values to match. +// /// The formatter to match against. +// /// A indicating the quality of the match or null is no match. +// protected virtual MediaTypeFormatterMatch MatchAcceptHeader(IEnumerable sortedAcceptValues, MediaTypeFormatter formatter) +// { +// if (sortedAcceptValues == null) +// { +// throw Error.ArgumentNull("sortedAcceptValues"); +// } +// if (formatter == null) +// { +// throw Error.ArgumentNull("formatter"); +// } + +// foreach (MediaTypeWithQualityHeaderValue acceptMediaTypeValue in sortedAcceptValues) +// { +// List supportedMediaTypes = formatter.SupportedMediaTypesInternal; +// 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; +// } + +// /// +// /// Match any request media type (in case there is a request entity body) against the formatter's registered +// /// media types. +// /// +// /// The request to match. +// /// The formatter to match against. +// /// A indicating the quality of the match or null is no match. +// protected virtual MediaTypeFormatterMatch MatchRequestMediaType(IOwinRequest request, MediaTypeFormatter formatter) +// { +// if (request == null) +// { +// throw new ArgumentNullException("request"); +// } +// if (formatter == null) +// { +// throw new ArgumentNullException("formatter"); +// } + +// if (request.Content != null) +// { +// MediaTypeHeaderValue requestMediaType = request.Content.Headers.ContentType; +// if (requestMediaType != null) +// { +// List supportedMediaTypes = formatter.SupportedMediaTypesInternal; +// 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; +// } + +// /// +// /// 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. +// /// +// /// The sorted accept header values to match. +// /// True if not ExcludeMatchOnTypeOnly and accept headers with a q-factor bigger than 0.0 are present. +// protected virtual bool ShouldMatchOnType(IEnumerable sortedAcceptValues) +// { +// if (sortedAcceptValues == null) +// { +// throw new ArgumentNullException("sortedAcceptValues"); +// } + +// return !(ExcludeMatchOnTypeOnly && sortedAcceptValues.Any()); +// } + +// /// +// /// Pick the first supported media type and indicate we've matched only on type +// /// +// /// The type to be serialized. +// /// The formatter we are matching against. +// /// A indicating the quality of the match or null is no match. +// protected virtual MediaTypeFormatterMatch MatchType(Type type, MediaTypeFormatter formatter) +// { +// // Performance-sensitive +// if (type == null) +// { +// throw new ArgumentNullException("type"); +// } +// if (formatter == null) +// { +// throw new ArgumentNullException("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 supportedMediaTypes = formatter.SupportedMediaTypesInternal; +// if (supportedMediaTypes.Count > 0) +// { +// mediaType = supportedMediaTypes[0]; +// } +// return new MediaTypeFormatterMatch(formatter, mediaType, FormattingUtilities.Match, MediaTypeFormatterMatchRanking.MatchOnCanWriteType); +// } + +// /// +// /// Sort Accept header values and related header field values with similar syntax rules +// /// (if more than 1) in descending order based on q-factor. +// /// +// /// The header values to sort. +// /// The sorted header values. +// protected virtual IEnumerable SortMediaTypeWithQualityHeaderValuesByQFactor(ICollection headerValues) +// { +// if (headerValues == null) +// { +// throw new ArgumentNullException("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; +// } +// } + +// /// +// /// 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. +// /// +// /// The header values to sort. +// /// The sorted header values. +// protected virtual IEnumerable SortStringWithQualityHeaderValuesByQFactor(ICollection headerValues) +// { +// if (headerValues == null) +// { +// throw new ArgumentNullException("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; +// } +// } + +// /// +// /// Evaluates whether a match is better than the current match and if so returns the replacement; otherwise returns the +// /// current match. +// /// +// 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 formatters) +// { +// Contract.Assert(formatters != null); +// MediaTypeFormatterCollection formatterCollection = formatters as MediaTypeFormatterCollection; +// if (formatterCollection != null) +// { +// return formatterCollection.WritingFormatters; +// } +// return formatters.AsArray(); +// } + + public ContentNegotiationResult Negotiate(Type type, IOwinContext context, IEnumerable formatters) + { + return new ContentNegotiationResult(formatters.First(), formatters.First().SupportedMediaTypes.First()); + } + } +} diff --git a/Microsoft.AspNet.Mvc/Extensions/IEnumerableExtensions.cs b/Microsoft.AspNet.Mvc/Extensions/IEnumerableExtensions.cs new file mode 100644 index 0000000000..fa064899e6 --- /dev/null +++ b/Microsoft.AspNet.Mvc/Extensions/IEnumerableExtensions.cs @@ -0,0 +1,20 @@ +using System.Diagnostics.Contracts; +using System.Linq; + +namespace System.Collections.Generic +{ + internal static class IEnumerableExtensions + { + public static T[] AsArray(this IEnumerable values) + { + Contract.Assert(values != null); + + T[] array = values as T[]; + if (array == null) + { + array = values.ToArray(); + } + return array; + } + } +} diff --git a/Microsoft.AspNet.Mvc/Extensions/TypeExtensions.cs b/Microsoft.AspNet.Mvc/Extensions/TypeExtensions.cs new file mode 100644 index 0000000000..9c0845d867 --- /dev/null +++ b/Microsoft.AspNet.Mvc/Extensions/TypeExtensions.cs @@ -0,0 +1,91 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Reflection; + +namespace Microsoft.AspNet.Mvc +{ + internal static class TypeExtensions + { +#if NETFX_CORE + private static bool EqualTo(this Type[] t1, Type[] t2) + { + if (t1.Length != t2.Length) + { + return false; + } + + for (int idx = 0; idx < t1.Length; ++idx) + { + if (t1[idx] != t2[idx]) + { + return false; + } + } + + return true; + } + + public static ConstructorInfo GetConstructor(this Type type, Type[] types) + { + return type.GetTypeInfo().DeclaredConstructors + .Where(c => c.IsPublic) + .SingleOrDefault(c => c.GetParameters() + .Select(p => p.ParameterType).ToArray().EqualTo(types)); + } +#endif + + public static Type ExtractGenericInterface(this Type queryType, Type interfaceType) + { + Func matchesInterface = t => t.IsGenericType() && t.GetGenericTypeDefinition() == interfaceType; + return (matchesInterface(queryType)) ? queryType : queryType.GetInterfaces().FirstOrDefault(matchesInterface); + } + +#if NETFX_CORE + public static Type[] GetGenericArguments(this Type type) + { + return type.GetTypeInfo().GenericTypeArguments; + } + + public static Type[] GetInterfaces(this Type type) + { + return type.GetTypeInfo().ImplementedInterfaces.ToArray(); + } +#endif + +#if NETFX_CORE + public static bool IsAssignableFrom(this Type type, Type c) + { + return type.GetTypeInfo().IsAssignableFrom(c.GetTypeInfo()); + } +#endif + + public static bool IsGenericType(this Type type) + { +#if NETFX_CORE + return type.GetTypeInfo().IsGenericType; +#else + return type.IsGenericType; +#endif + } + + public static bool IsInterface(this Type type) + { +#if NETFX_CORE + return type.GetTypeInfo().IsInterface; +#else + return type.IsInterface; +#endif + } + + public static bool IsValueType(this Type type) + { +#if NETFX_CORE + return type.GetTypeInfo().IsValueType; +#else + return type.IsValueType; +#endif + } + } +} diff --git a/Microsoft.AspNet.Mvc/Formatters/JQeryMvcForUrlEncodedFormatter.cs b/Microsoft.AspNet.Mvc/Formatters/JQeryMvcForUrlEncodedFormatter.cs new file mode 100644 index 0000000000..96db73e393 --- /dev/null +++ b/Microsoft.AspNet.Mvc/Formatters/JQeryMvcForUrlEncodedFormatter.cs @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Net.Http; +using System.Net.Http.Formatting; +using System.Threading.Tasks; + +namespace Microsoft.AspNet.Mvc +{ + // Supports JQuery schema on FormURL. + public class JQueryMvcFormUrlEncodedFormatter : FormUrlEncodedMediaTypeFormatter + { + public override bool CanReadType(Type type) + { + if (type == null) + { + throw new ArgumentNullException("type"); + } + + return true; + } + + public override Task ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger) + { + if (type == null) + { + throw new ArgumentNullException("type"); + } + + if (readStream == null) + { + throw new ArgumentNullException("readStream"); + } + + // For simple types, defer to base class + if (base.CanReadType(type)) + { + return base.ReadFromStreamAsync(type, readStream, content, formatterLogger); + } + + return ReadFromStreamAsyncCore(type, readStream, content, formatterLogger); + } + + private async Task ReadFromStreamAsyncCore(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger) + { + object obj = await base.ReadFromStreamAsync(typeof(FormDataCollection), readStream, content, formatterLogger); + FormDataCollection fd = (FormDataCollection)obj; + + try + { + throw new NotImplementedException(); + //return fd.ReadAs(type, String.Empty, RequiredMemberSelector, formatterLogger); + } + catch (Exception e) + { + if (formatterLogger == null) + { + throw; + } + formatterLogger.LogError(String.Empty, e); + return GetDefaultValueForType(type); + } + } + } +} diff --git a/Microsoft.AspNet.Mvc/FormattingUtilities.cs b/Microsoft.AspNet.Mvc/FormattingUtilities.cs new file mode 100644 index 0000000000..a04f54de16 --- /dev/null +++ b/Microsoft.AspNet.Mvc/FormattingUtilities.cs @@ -0,0 +1,238 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Net.Http.Formatting; +using System.Net.Http.Headers; +using System.Runtime.Serialization; +using Newtonsoft.Json.Linq; +using System; +using System.Net.Http; + +namespace Microsoft.AspNet.Mvc +{ + /// + /// Provides various internal utility functions + /// + 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 = "()<>@,;:\\\"/[]?={}"; + + /// + /// Quality factor to indicate a perfect match. + /// + public const double Match = 1.0; + + /// + /// Quality factor to indicate no match. + /// + public const double NoMatch = 0.0; + + /// + /// The default max depth for our formatter is 256 + /// + public const int DefaultMaxDepth = 256; + + /// + /// The default min depth for our formatter is 1 + /// + public const int DefaultMinDepth = 1; + + /// + /// HTTP X-Requested-With header field name + /// + public const string HttpRequestedWithHeader = @"x-requested-with"; + + /// + /// HTTP X-Requested-With header field value + /// + public const string HttpRequestedWithHeaderValue = @"XMLHttpRequest"; + + /// + /// HTTP Host header field name + /// + public const string HttpHostHeader = "Host"; + + /// + /// HTTP Version token + /// + public const string HttpVersionToken = "HTTP"; + + ///// + ///// A representing . + ///// + //public static readonly Type HttpRequestMessageType = typeof(HttpRequestMessage); + + ///// + ///// A representing . + ///// + //public static readonly Type HttpResponseMessageType = typeof(HttpResponseMessage); + + ///// + ///// A representing . + ///// + //public static readonly Type HttpContentType = typeof(HttpContent); + + /// + /// A representing . + /// + public static readonly Type DelegatingEnumerableGenericType = typeof(DelegatingEnumerable<>); + + /// + /// A representing . + /// + public static readonly Type EnumerableInterfaceGenericType = typeof(IEnumerable<>); + + /// + /// A representing . + /// + public static readonly Type QueryableInterfaceGenericType = typeof(IQueryable<>); + + /// + /// Determines whether is a type. + /// + /// The type to test. + /// + /// true if is a type; otherwise, false. + /// + public static bool IsJTokenType(Type type) + { + return typeof(JToken).IsAssignableFrom(type); + } + + /// + /// Creates an empty instance. The only way is to get it from a dummy + /// instance. + /// + /// The created instance. + 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; + } + +//#if NETFX_CORE // MaxDepth is a DOS mitigation. We don't support MaxDepth in portable libraries because it is strictly client side. + +// /// +// /// Create a default reader quotas with a default depth quota of 1K +// /// +// /// +// public static XmlDictionaryReaderQuotas CreateDefaultReaderQuotas() +// { +// return XmlDictionaryReaderQuotas.Max; +//#else +// return new XmlDictionaryReaderQuotas() +// { +// MaxArrayLength = Int32.MaxValue, +// MaxBytesPerRead = Int32.MaxValue, +// MaxDepth = DefaultMaxDepth, +// MaxNameTableCharCount = Int32.MaxValue, +// MaxStringContentLength = Int32.MaxValue +// }; +//#endif +// } + + /// + /// Remove bounding quotes on a token if present + /// + /// Token to unquote. + /// Unquoted token. + 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); + } + + /// + /// Parses valid integer strings with no leading signs, whitespace or other + /// + /// The value to parse + /// The result + /// True if value was valid; false otherwise. + public static bool TryParseInt32(string value, out int result) + { + return Int32.TryParse(value, NumberStyles.None, NumberFormatInfo.InvariantInfo, out result); + } + } +} diff --git a/Microsoft.AspNet.Mvc/IOwinContentNegotiator.cs b/Microsoft.AspNet.Mvc/IOwinContentNegotiator.cs new file mode 100644 index 0000000000..703f2ecd4f --- /dev/null +++ b/Microsoft.AspNet.Mvc/IOwinContentNegotiator.cs @@ -0,0 +1,12 @@ +using Microsoft.Owin; +using System; +using System.Collections.Generic; +using System.Net.Http.Formatting; + +namespace Microsoft.AspNet.Mvc +{ + public interface IOwinContentNegotiator + { + ContentNegotiationResult Negotiate(Type type, IOwinContext context, IEnumerable formatters); + } +}