diff --git a/Mvc.sln b/Mvc.sln
index ef7698c239..dad54ecc62 100644
--- a/Mvc.sln
+++ b/Mvc.sln
@@ -2,6 +2,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.21901.1
+VisualStudioVersion = 14.0.21806.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}"
EndProject
@@ -54,6 +55,9 @@ EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "RazorWebSite", "test\WebSites\RazorWebSite\RazorWebSite.kproj", "{B07CAF59-11ED-40E3-A5DB-E1178F84FA78}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ValueProvidersSite", "test\WebSites\ValueProvidersSite\ValueProvidersSite.kproj", "{14F79E79-AE79-48FA-95DE-D794EF4EABB3}"
+Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.HeaderValueAbstractions", "src\Microsoft.AspNet.Mvc.HeaderValueAbstractions\Microsoft.AspNet.Mvc.HeaderValueAbstractions.kproj", "{98335B23-E4B9-4CAD-9749-0DED32A659A1}"
+EndProject
+Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.HeaderValueAbstractions.Tests", "test\Microsoft.AspNet.Mvc.HeaderValueAbstractions.Test\Microsoft.AspNet.Mvc.HeaderValueAbstractions.Tests.kproj", "{E69FD235-2042-43A4-9970-59CB29955B4E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -285,6 +289,26 @@ Global
{14F79E79-AE79-48FA-95DE-D794EF4EABB3}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{14F79E79-AE79-48FA-95DE-D794EF4EABB3}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{14F79E79-AE79-48FA-95DE-D794EF4EABB3}.Release|x86.ActiveCfg = Release|Any CPU
+ {98335B23-E4B9-4CAD-9749-0DED32A659A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {98335B23-E4B9-4CAD-9749-0DED32A659A1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {98335B23-E4B9-4CAD-9749-0DED32A659A1}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {98335B23-E4B9-4CAD-9749-0DED32A659A1}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {98335B23-E4B9-4CAD-9749-0DED32A659A1}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {98335B23-E4B9-4CAD-9749-0DED32A659A1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {98335B23-E4B9-4CAD-9749-0DED32A659A1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {98335B23-E4B9-4CAD-9749-0DED32A659A1}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {98335B23-E4B9-4CAD-9749-0DED32A659A1}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {98335B23-E4B9-4CAD-9749-0DED32A659A1}.Release|x86.ActiveCfg = Release|Any CPU
+ {E69FD235-2042-43A4-9970-59CB29955B4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E69FD235-2042-43A4-9970-59CB29955B4E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E69FD235-2042-43A4-9970-59CB29955B4E}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {E69FD235-2042-43A4-9970-59CB29955B4E}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {E69FD235-2042-43A4-9970-59CB29955B4E}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {E69FD235-2042-43A4-9970-59CB29955B4E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E69FD235-2042-43A4-9970-59CB29955B4E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E69FD235-2042-43A4-9970-59CB29955B4E}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {E69FD235-2042-43A4-9970-59CB29955B4E}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {E69FD235-2042-43A4-9970-59CB29955B4E}.Release|x86.ActiveCfg = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -313,5 +337,7 @@ Global
{A853B2BA-4449-4908-A416-5A3C027FC22B} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{B07CAF59-11ED-40E3-A5DB-E1178F84FA78} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{14F79E79-AE79-48FA-95DE-D794EF4EABB3} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
+ {98335B23-E4B9-4CAD-9749-0DED32A659A1} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
+ {E69FD235-2042-43A4-9970-59CB29955B4E} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
EndGlobalSection
EndGlobal
diff --git a/src/Microsoft.AspNet.Mvc.HeaderValueAbstractions/FormattingUtilities.cs b/src/Microsoft.AspNet.Mvc.HeaderValueAbstractions/FormattingUtilities.cs
new file mode 100644
index 0000000000..f36bb61559
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.HeaderValueAbstractions/FormattingUtilities.cs
@@ -0,0 +1,18 @@
+// 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.AspNet.Mvc.HeaderValueAbstractions
+{
+ public static class FormattingUtilities
+ {
+ ///
+ /// 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;
+ }
+}
diff --git a/src/Microsoft.AspNet.Mvc.HeaderValueAbstractions/MediaTypeHeaderValue.cs b/src/Microsoft.AspNet.Mvc.HeaderValueAbstractions/MediaTypeHeaderValue.cs
new file mode 100644
index 0000000000..ed3e5691a2
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.HeaderValueAbstractions/MediaTypeHeaderValue.cs
@@ -0,0 +1,187 @@
+// 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.Text;
+
+namespace Microsoft.AspNet.Mvc.HeaderValueAbstractions
+{
+ public class MediaTypeHeaderValue
+ {
+ public string Charset { get; set; }
+
+ public string MediaType { get; set; }
+
+ public string MediaSubType { get; set; }
+
+ public MediaTypeHeaderValueRange MediaTypeRange { get; set; }
+
+ public string RawValue
+ {
+ get
+ {
+ var stringBuilder = new StringBuilder();
+ stringBuilder.Append(MediaType);
+ stringBuilder.Append('/');
+ stringBuilder.Append(MediaSubType);
+ if (!string.IsNullOrEmpty(Charset))
+ {
+ stringBuilder.Append(";charset=");
+ stringBuilder.Append(Charset);
+ }
+
+ foreach (var parameter in Parameters)
+ {
+ if (string.Equals(parameter.Key, "charset", System.StringComparison.OrdinalIgnoreCase))
+ {
+ continue;
+ }
+
+ stringBuilder.Append(";");
+ stringBuilder.Append(parameter.Key);
+ stringBuilder.Append("=");
+ stringBuilder.Append(parameter.Value);
+ }
+
+ return stringBuilder.ToString();
+ }
+ }
+
+ public IDictionary Parameters { get; set; }
+
+ public static MediaTypeHeaderValue Parse(string input)
+ {
+ if (string.IsNullOrEmpty(input))
+ {
+ return null;
+ }
+
+ var inputArray = input.Split(new[] { ';' }, 2);
+ var mediaTypeParts = inputArray[0].Split('/');
+ if (mediaTypeParts.Length != 2)
+ {
+ return null;
+ }
+
+ // TODO: throw if the media type and subtypes are invalid.
+ var mediaType = mediaTypeParts[0].Trim();
+ var mediaSubType = mediaTypeParts[1].Trim();
+ var mediaTypeRange = MediaTypeHeaderValueRange.None;
+ if (mediaType == "*" && mediaSubType == "*")
+ {
+ mediaTypeRange = MediaTypeHeaderValueRange.AllMediaRange;
+ }
+ else if (mediaSubType == "*")
+ {
+ mediaTypeRange = MediaTypeHeaderValueRange.SubtypeMediaRange;
+ }
+
+ Dictionary parameters = null;
+ string charset = null;
+ if (inputArray.Length == 2)
+ {
+ parameters = ParseParameters(inputArray[1]);
+ parameters.TryGetValue("charset", out charset);
+ }
+
+ var mediaTypeHeader = new MediaTypeHeaderValue()
+ {
+ MediaType = mediaType,
+ MediaSubType = mediaSubType,
+ MediaTypeRange = mediaTypeRange,
+ Charset = charset,
+ Parameters = parameters ?? new Dictionary(StringComparer.OrdinalIgnoreCase),
+ };
+
+ return mediaTypeHeader;
+ }
+
+ protected static Dictionary ParseParameters(string inputString)
+ {
+ var acceptParameters = inputString.Split(';');
+ var parameterNameValue = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ foreach (var parameter in acceptParameters)
+ {
+ var index = parameter.Split('=');
+ if (index.Length == 2)
+ {
+ // TODO: throw exception if this is not the case.
+ parameterNameValue.Add(index[0].Trim(), index[1].Trim());
+ }
+ }
+
+ return parameterNameValue;
+ }
+
+ ///
+ /// Determines whether this instance is a subset of passed .
+ /// If the media type and media type parameters of this media type are all present
+ /// and match those of then it is a match even though
+ /// may have additional parameters.
+ ///
+ /// The first media type.
+ /// The second media type.
+ /// true if this is a subset of ; false otherwise.
+ public bool IsSubsetOf(MediaTypeHeaderValue otherMediaType)
+ {
+ if (otherMediaType == null)
+ {
+ return false;
+ }
+
+ if (!MediaType.Equals(otherMediaType.MediaType, StringComparison.OrdinalIgnoreCase))
+ {
+ if (otherMediaType.MediaTypeRange != MediaTypeHeaderValueRange.AllMediaRange)
+ {
+ return false;
+ }
+ }
+ else if (!MediaSubType.Equals(otherMediaType.MediaSubType, StringComparison.OrdinalIgnoreCase))
+ {
+ if (otherMediaType.MediaTypeRange != MediaTypeHeaderValueRange.SubtypeMediaRange)
+ {
+ return false;
+ }
+ }
+
+ if (Parameters != null)
+ {
+ if (Parameters.Count != 0 &&
+ (otherMediaType.Parameters == null || otherMediaType.Parameters.Count == 0))
+ {
+ 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
+ if(!MatchParameters(Parameters, otherMediaType.Parameters))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private static bool MatchParameters(IDictionary parameters1,
+ IDictionary parameters2)
+ {
+ foreach (var parameterKey in parameters1.Keys)
+ {
+ string parameterValue2 = null;
+ if (!parameters2.TryGetValue(parameterKey, out parameterValue2))
+ {
+ return false;
+ }
+
+ if (parameterValue2 == null || !parameterValue2.Equals(parameters1[parameterKey]))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.Mvc.HeaderValueAbstractions/MediaTypeHeaderValueRange.cs b/src/Microsoft.AspNet.Mvc.HeaderValueAbstractions/MediaTypeHeaderValueRange.cs
new file mode 100644
index 0000000000..11bbeb5712
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.HeaderValueAbstractions/MediaTypeHeaderValueRange.cs
@@ -0,0 +1,23 @@
+// 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.AspNet.Mvc
+{
+ public enum MediaTypeHeaderValueRange
+ {
+ ///
+ /// Not a media type range
+ ///
+ None = 0,
+
+ ///
+ /// A subtype media range, e.g. "application/*".
+ ///
+ SubtypeMediaRange,
+
+ ///
+ /// An all media range, e.g. "*/*".
+ ///
+ AllMediaRange,
+ }
+}
diff --git a/src/Microsoft.AspNet.Mvc.HeaderValueAbstractions/MediaTypeWithQualityHeaderValue.cs b/src/Microsoft.AspNet.Mvc.HeaderValueAbstractions/MediaTypeWithQualityHeaderValue.cs
new file mode 100644
index 0000000000..cef3eac13d
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.HeaderValueAbstractions/MediaTypeWithQualityHeaderValue.cs
@@ -0,0 +1,43 @@
+// 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;
+
+namespace Microsoft.AspNet.Mvc.HeaderValueAbstractions
+{
+ public class MediaTypeWithQualityHeaderValue : MediaTypeHeaderValue
+ {
+ public double? Quality { get; private set; }
+
+ public static new MediaTypeWithQualityHeaderValue Parse(string input)
+ {
+ var mediaTypeHeaderValue = MediaTypeHeaderValue.Parse(input);
+ if (mediaTypeHeaderValue == null)
+ {
+ return null;
+ }
+
+ var quality = FormattingUtilities.Match;
+ string qualityStringValue = null;
+ if (mediaTypeHeaderValue.Parameters.TryGetValue("q", out qualityStringValue))
+ {
+ if(!Double.TryParse(qualityStringValue, out quality))
+ {
+ return null;
+ }
+ }
+
+ return
+ new MediaTypeWithQualityHeaderValue()
+ {
+ MediaType = mediaTypeHeaderValue.MediaType,
+ MediaSubType = mediaTypeHeaderValue.MediaSubType,
+ MediaTypeRange = mediaTypeHeaderValue.MediaTypeRange,
+ Charset = mediaTypeHeaderValue.Charset,
+ Parameters = mediaTypeHeaderValue.Parameters,
+ Quality = quality,
+ };
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.Mvc.HeaderValueAbstractions/Microsoft.AspNet.Mvc.HeaderValueAbstractions.kproj b/src/Microsoft.AspNet.Mvc.HeaderValueAbstractions/Microsoft.AspNet.Mvc.HeaderValueAbstractions.kproj
new file mode 100644
index 0000000000..d9ecfd883f
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.HeaderValueAbstractions/Microsoft.AspNet.Mvc.HeaderValueAbstractions.kproj
@@ -0,0 +1,30 @@
+
+
+
+ 12.0
+ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+
+
+
+ 98335b23-e4b9-4cad-9749-0ded32a659a1
+ Library
+
+
+
+
+
+
+ 2.0
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.HeaderValueAbstractions/StringWithQualityHeaderValue.cs b/src/Microsoft.AspNet.Mvc.HeaderValueAbstractions/StringWithQualityHeaderValue.cs
new file mode 100644
index 0000000000..327c60f205
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.HeaderValueAbstractions/StringWithQualityHeaderValue.cs
@@ -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;
+
+namespace Microsoft.AspNet.Mvc.HeaderValueAbstractions
+{
+ public class StringWithQualityHeaderValue
+ {
+ public double? Quality { get; set; }
+
+ public string RawValue { get; set; }
+
+ public string Value { get; set; }
+
+ public static StringWithQualityHeaderValue Parse(string input)
+ {
+ var inputArray = input.Split(new[] { ';' }, 2);
+ var value = inputArray[0].Trim();
+
+ // Unspecified q factor value is equal to a match.
+ var quality = FormattingUtilities.Match;
+ if (inputArray.Length > 1)
+ {
+ var parameter = inputArray[1].Trim();
+ var nameValuePair = parameter.Split(new[] { '=' }, 2);
+ if (nameValuePair.Length > 1 && nameValuePair[0].Trim().Equals("q"))
+ {
+ // TODO: all extraneous parameters are ignored. Throw/return null if that is the case.
+ if(!Double.TryParse(nameValuePair[1].Trim(), out quality))
+ {
+ return null;
+ }
+ }
+ }
+
+ var stringWithQualityHeader = new StringWithQualityHeaderValue()
+ {
+ Quality = quality,
+ Value = value,
+ RawValue = input
+ };
+
+ return stringWithQualityHeader;
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.Mvc.HeaderValueAbstractions/project.json b/src/Microsoft.AspNet.Mvc.HeaderValueAbstractions/project.json
new file mode 100644
index 0000000000..3e937c23ae
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.HeaderValueAbstractions/project.json
@@ -0,0 +1,14 @@
+{
+ "version": "1.0.0-*",
+ "dependencies": {},
+ "configurations": {
+ "net45": {},
+ "k10": {
+ "dependencies": {
+ "System.Collections": "4.0.0.0",
+ "System.Runtime": "4.0.20.0",
+ "System.Runtime.Extensions": "4.0.10.0"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json b/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json
index 717d9af643..8c4c09f25a 100644
--- a/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json
+++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json
@@ -13,6 +13,7 @@
"Microsoft.Framework.ConfigurationModel.Json": "1.0.0-*",
"Microsoft.Framework.DependencyInjection": "1.0.0-*",
"Microsoft.Framework.Runtime.Interfaces": "1.0.0-*",
+ "Microsoft.AspNet.PipelineCore": "1.0.0-*",
"RoutingWebSite": "",
"RazorWebSite": "",
"ValueProvidersSite": "",
diff --git a/test/Microsoft.AspNet.Mvc.HeaderValueAbstractions.Test/MediaTypeHeaderValueParsingTests.cs b/test/Microsoft.AspNet.Mvc.HeaderValueAbstractions.Test/MediaTypeHeaderValueParsingTests.cs
new file mode 100644
index 0000000000..96779feb76
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.HeaderValueAbstractions.Test/MediaTypeHeaderValueParsingTests.cs
@@ -0,0 +1,135 @@
+// 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 Xunit;
+
+namespace Microsoft.AspNet.Mvc.HeaderValueAbstractions
+{
+ public class MediaTypeHeaderValueParsingTests
+ {
+ public static IEnumerable