您的位置:首页 > 编程语言 > ASP

ASP.NET WebAPI 自动格式转换详细说明

2012-12-07 18:14 525 查看
最近在项目上使用了ASP.NET WebAPI 代替原有使用MVC3开发的接口, 原因就是因为WebAPI支持SelftHost, 在安装到客户端时无需安装IIS, 免去很多麻烦.   

  但是发布后好多童鞋不明白WebAPI的自动格式转换规则是怎样的, 所以在这里特别拿出来说明一下.

简单说明:

规则以优先级排序:

1.MatchOnRequestWithMediaTypeMapping, 即没有Accept头, 或者只有一个并为 */*, 这种情况能够匹配任何Formatter

2.MatchOnRequestAcceptHeader(这里三种头部优先级一样), 即Accept匹配, */*, text/json, application/json, 如果为*/*则匹配所有Formatter, 如果Formatter支持的类型为text/json,

  text/json即大类与小类都与Formatter匹配, 如果Formatter支持的类型为text/json, accept为application/json即小类匹配

3.MatchOnRequestMediaType, 即没有Accept头匹配, 但Content Accept头匹配

4.MatchOnCanWriteType, 即最勉强的匹配

5.如果同样类型的匹配出现多个, 即比较他们的Quality值, 最大值优先

详细说明开始

1.首先在执行完Action后, WebAPI会调用ValueResultConverter类的Converter方法对Action的返回值进行序列化操作, 以下红色代码就是会将返回值转换为一个HttpResponseMessage对象

1 public class ValueResultConverter<T> : IActionResultConverter
2     {
3         public HttpResponseMessage Convert(HttpControllerContext controllerContext, object actionResult)
4         {
5             if (controllerContext == null)
6             {
7                 throw Error.ArgumentNull("controllerContext");
8             }
9
10             HttpResponseMessage resultAsResponse = actionResult as HttpResponseMessage;
11             if (resultAsResponse != null)
12             {
13                 resultAsResponse.EnsureResponseHasRequest(controllerContext.Request);
14                 return resultAsResponse;
15             }
16
17             T value = (T)actionResult;
18             return controllerContext.Request.CreateResponse<T>(HttpStatusCode.OK, value, controllerContext.Configuration);
19         }
20     }


2.之后将会进入DefaultContentNegotiator类的Negotiate方法, 我们可以看到标红的两句代码, ComputeFormatterMatches方法传入要转换的类型, 请求对象与所有的Formatter, 来获取所有匹配的Formatter,

在这里大家应该可以预想到, 如何匹配Formatter就是根据要转换对象的类型, 请求对象的属性, 还有所有格式化器决定的.

我们可以看到ComputeFormatterMatches方法返回的是集合对象, 但是最终我们只会返回一个结果, 因此只能找出一个最为匹配的Formatter, 因此程序还会执行以下代码来找到最匹配的Formatter.

MediaTypeFormatterMatch bestFormatterMatch = SelectResponseMediaTypeFormatter(matches);


public virtual ContentNegotiationResult Negotiate(Type type, HttpRequestMessage request, IEnumerable<MediaTypeFormatter> formatters)
{
if (type == null)
{
throw Error.ArgumentNull("type");
}
if (request == null)
{
throw Error.ArgumentNull("request");
}
if (formatters == null)
{
throw Error.ArgumentNull("formatters");
}

// If formatter list is empty then we won't find a match
if (!formatters.Any())
{
return null;
}

// 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;
}


接下来我们进入到ComputeFormatterMatches方法中, 来查看一下究竟是如何判断返回对象与哪个Formatter匹配的.

protected virtual Collection<MediaTypeFormatterMatch> ComputeFormatterMatches(Type type, HttpRequestMessage request, IEnumerable<MediaTypeFormatter> formatters)
{
if (type == null)
{
throw Error.ArgumentNull("type");
}
if (request == null)
{
throw Error.ArgumentNull("request");
}
if (formatters == null)
{
throw Error.ArgumentNull("formatters");
}

IEnumerable<MediaTypeWithQualityHeaderValue> sortedAcceptValues = null;

// Go through each formatter to find how well it matches.
Collection<MediaTypeFormatterMatch> matches = new Collection<MediaTypeFormatterMatch>();
foreach (MediaTypeFormatter formatter in formatters)
{
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;
}


foreach (MediaTypeFormatter formatter in formatters)会遍历所有Formatter, 在循环中进行以下匹配判断:

  1. formatter.CanWriteType(type), 判断类型是否为Formatter所支持的类型, 如果不是则继续下一循环.

   2.MatchMediaTypeMapping(request, formatter), 大家可以查看以下代码的IF判断, 如果请求中不包含Accept头, 或者仅有一个并且为*/*时, 证明该Formatter匹配

public override double TryMatchMediaType(HttpRequestMessage request)
{
if (request == null)
{
throw Error.ArgumentNull("request");
}

// Accept header trumps XHR mapping.
// Accept: */* is equivalent to passing no Accept header.
if (request.Headers.Accept.Count == 0
|| (request.Headers.Accept.Count == 1 && request.Headers.Accept.First().MediaType.Equals("*/*", StringComparison.Ordinal)))
{
return base.TryMatchMediaType(request);
}
else
{
return FormattingUtilities.NoMatch;
}
}


  3.MatchAcceptHeader(sortedAcceptValues, formatter), 判断Accept头是否与Formatter匹配

protected virtual MediaTypeFormatterMatch MatchAcceptHeader(IEnumerable<MediaTypeWithQualityHeaderValue> sortedAcceptValues, MediaTypeFormatter formatter)
{
if (sortedAcceptValues == null)
{
throw Error.ArgumentNull("sortedAcceptValues");
}
if (formatter == null)
{
throw Error.ArgumentNull("formatter");
}

foreach (MediaTypeWithQualityHeaderValue acceptMediaTypeValue in sortedAcceptValues)
{
foreach (MediaTypeHeaderValue supportedMediaType in formatter.SupportedMediaTypes)
{
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;
}


  4. MatchRequestMediaType(request, formatter), 判断内容头部是否与Formatter匹配

protected virtual MediaTypeFormatterMatch MatchRequestMediaType(HttpRequestMessage request, MediaTypeFormatter formatter)
{
if (request == null)
{
throw Error.ArgumentNull("request");
}
if (formatter == null)
{
throw Error.ArgumentNull("formatter");
}

if (request.Content != null)
{
MediaTypeHeaderValue requestMediaType = request.Content.Headers.ContentType;
if (requestMediaType != null)
{
foreach (MediaTypeHeaderValue supportedMediaType in formatter.SupportedMediaTypes)
{
if (supportedMediaType != null && supportedMediaType.IsSubsetOf(requestMediaType))
{
return new MediaTypeFormatterMatch(formatter, supportedMediaType, FormattingUtilities.Match, MediaTypeFormatterMatchRanking.MatchOnRequestMediaType);
}
}
}
}

return null;
}


  5.如果以上都不匹配, 则判断在配置上想要返回406错误, 还是强行使用Formatter进行格式化

protected virtual bool ShouldMatchOnType(IEnumerable<MediaTypeWithQualityHeaderValue> sortedAcceptValues)
{
if (sortedAcceptValues == null)
{
throw Error.ArgumentNull("sortedAcceptValues");
}

return !(ExcludeMatchOnTypeOnly && sortedAcceptValues.Any());
}


最后就要找出最为匹配的Formatter了, 代码我就不详细描述了, 规则以优先级排序:

1.MatchOnRequestWithMediaTypeMapping, 即没有Accept头, 或者只有一个并为 */*

2.MatchOnRequestAcceptHeader(这里三种头部优先级一样), 即Accept匹配, */*, text/json, application/json

3.MatchOnRequestMediaType, 即没有Accept头匹配, 但Content Accept头匹配

4.MatchOnCanWriteType, 即最勉强的匹配

5.如果同样类型的匹配出现多个, 即比较他们的Quality值, 最大值优先

protected virtual MediaTypeFormatterMatch SelectResponseMediaTypeFormatter(ICollection<MediaTypeFormatterMatch> matches)
{
if (matches == null)
{
throw Error.ArgumentNull("matches");
}

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.
foreach (MediaTypeFormatterMatch match in matches)
{
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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐