[Fixes #1429] Content negotiation does a case-sensitive matching of media type's parameter values
This commit is contained in:
parent
2d32420f01
commit
17e4dd2bf6
|
|
@ -187,7 +187,7 @@ namespace Microsoft.AspNet.Mvc.HeaderValueAbstractions
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parameterValue2 == null || !parameterValue2.Equals(parameters1[parameterKey]))
|
if (!string.Equals(parameterValue2, parameters1[parameterKey], StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,33 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
Assert.Equal(HttpStatusCode.NotAcceptable, response.StatusCode);
|
Assert.Equal(HttpStatusCode.NotAcceptable, response.StatusCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("ContactInfoUsingV3Format", "text/vcard; charset=utf-8; version=v3.0", "BEGIN:VCARD#FN:John Williams#END:VCARD#")]
|
||||||
|
[InlineData("ContactInfoUsingV4Format", "text/vcard; charset=utf-8; version=v4.0", "BEGIN:VCARD#FN:John Williams#GENDER:M#END:VCARD#")]
|
||||||
|
public async Task ProducesAttribute_WithMediaTypeHavingParameters_IsCaseInsensitiveMatch(
|
||||||
|
string action,
|
||||||
|
string expectedMediaType,
|
||||||
|
string expectedResponseBody)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_provider, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
expectedResponseBody = expectedResponseBody.Replace("#", Environment.NewLine);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetAsync("http://localhost/ProducesWithMediaTypeParameters/" + action);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
Assert.NotNull(response.Content);
|
||||||
|
var contentType = response.Content.Headers.ContentType;
|
||||||
|
Assert.NotNull(contentType);
|
||||||
|
Assert.Equal(expectedMediaType, contentType.ToString());
|
||||||
|
|
||||||
|
var actualResponseBody = await response.Content.ReadAsStringAsync();
|
||||||
|
Assert.Equal(expectedResponseBody, actualResponseBody);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ProducesContentAttribute_OnAction_OverridesTheValueOnClass()
|
public async Task ProducesContentAttribute_OnAction_OverridesTheValueOnClass()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,12 @@ namespace Microsoft.AspNet.Mvc.HeaderValueAbstractions
|
||||||
[InlineData("text/plain;", "*/*;charset=utf-8;", true)]
|
[InlineData("text/plain;", "*/*;charset=utf-8;", true)]
|
||||||
[InlineData("text/plain;", "text/*;charset=utf-8;", true)]
|
[InlineData("text/plain;", "text/*;charset=utf-8;", true)]
|
||||||
[InlineData("text/plain;", "text/plain;charset=utf-8;", true)]
|
[InlineData("text/plain;", "text/plain;charset=utf-8;", true)]
|
||||||
|
[InlineData("text/plain;version=v1", "text/plain;version=", false)]
|
||||||
|
[InlineData("text/plain;version=v1", "Text/plain;Version=v1", true)]
|
||||||
|
[InlineData("text/plain;version=v1", "tExT/plain;version=V1", true)]
|
||||||
|
[InlineData("text/plain;version=v1", "TEXT/PLAIN;VERSION=V1", true)]
|
||||||
[InlineData("text/plain;charset=utf-8;foo=bar;q=0.0", "text/plain;charset=utf-8;foo=bar;q=0.0", true)]
|
[InlineData("text/plain;charset=utf-8;foo=bar;q=0.0", "text/plain;charset=utf-8;foo=bar;q=0.0", true)]
|
||||||
|
[InlineData("text/plain;charset=utf-8;foo=bar;q=0.0", "text/plain;foo=bar;q=0.0;charset=utf-8", true)] // different order of parameters
|
||||||
[InlineData("text/plain;charset=utf-8;foo=bar;q=0.0", "text/*;charset=utf-8;foo=bar;q=0.0", true)]
|
[InlineData("text/plain;charset=utf-8;foo=bar;q=0.0", "text/*;charset=utf-8;foo=bar;q=0.0", true)]
|
||||||
[InlineData("text/plain;charset=utf-8;foo=bar;q=0.0", "*/*;charset=utf-8;foo=bar;q=0.0", true)]
|
[InlineData("text/plain;charset=utf-8;foo=bar;q=0.0", "*/*;charset=utf-8;foo=bar;q=0.0", true)]
|
||||||
[InlineData("*/*;", "text/plain;charset=utf-8;foo=bar;q=0.0", false)]
|
[InlineData("*/*;", "text/plain;charset=utf-8;foo=bar;q=0.0", false)]
|
||||||
|
|
@ -111,8 +116,8 @@ namespace Microsoft.AspNet.Mvc.HeaderValueAbstractions
|
||||||
[InlineData("text/plain;missingparam=4;", "text/plain;charset=utf-8;foo=bar;q=0.0", false)]
|
[InlineData("text/plain;missingparam=4;", "text/plain;charset=utf-8;foo=bar;q=0.0", false)]
|
||||||
[InlineData("text/plain;missingparam=4;", "text/*;charset=utf-8;foo=bar;q=0.0", false)]
|
[InlineData("text/plain;missingparam=4;", "text/*;charset=utf-8;foo=bar;q=0.0", false)]
|
||||||
[InlineData("text/plain;missingparam=4;", "*/*;charset=utf-8;foo=bar;q=0.0", false)]
|
[InlineData("text/plain;missingparam=4;", "*/*;charset=utf-8;foo=bar;q=0.0", false)]
|
||||||
public void MediaTypeHeaderValue_IsSubTypeTests(string mediaType1,
|
public void MediaTypeHeaderValue_IsSubTypeTests(string mediaType1, // Example: Formatter's supported media type
|
||||||
string mediaType2,
|
string mediaType2, // Example: Accept header media type
|
||||||
bool isMediaType1Subset)
|
bool isMediaType1Subset)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|
@ -132,6 +137,8 @@ namespace Microsoft.AspNet.Mvc.HeaderValueAbstractions
|
||||||
[InlineData("text/plain;charset=utf-16;foo=bar", "text/json;charset=utf-16;foo=bar")]
|
[InlineData("text/plain;charset=utf-16;foo=bar", "text/json;charset=utf-16;foo=bar")]
|
||||||
[InlineData("text/plain;charset=utf-16;foo=bar", "application/plain;charset=utf-16;foo=bar")]
|
[InlineData("text/plain;charset=utf-16;foo=bar", "application/plain;charset=utf-16;foo=bar")]
|
||||||
[InlineData("text/plain;charset=utf-16;foo=bar", "application/json;charset=utf-8;foo=bar1")]
|
[InlineData("text/plain;charset=utf-16;foo=bar", "application/json;charset=utf-8;foo=bar1")]
|
||||||
|
[InlineData("text/plain; charset=utf-16; foo=bar", "application/json;charset=utf-8;foo=bar1")]
|
||||||
|
[InlineData("text/plain;charset = utf-16;foo = bar", "application/json;charset=utf-8;foo=bar1")]
|
||||||
public void MediaTypeHeaderValue_UpdateValue_RawValueGetsUpdated(string mediaTypeValue,
|
public void MediaTypeHeaderValue_UpdateValue_RawValueGetsUpdated(string mediaTypeValue,
|
||||||
string expectedRawValue)
|
string expectedRawValue)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||||
|
|
@ -11,4 +11,4 @@
|
||||||
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
|
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||||
</Project>
|
</Project>
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
// 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 ConnegWebsite.Models;
|
||||||
|
using Microsoft.AspNet.Mvc;
|
||||||
|
|
||||||
|
namespace ConnegWebsite
|
||||||
|
{
|
||||||
|
public class ProducesWithMediaTypeParametersController : Controller
|
||||||
|
{
|
||||||
|
public override void OnActionExecuted(ActionExecutedContext context)
|
||||||
|
{
|
||||||
|
var result = context.Result as ObjectResult;
|
||||||
|
|
||||||
|
if (result != null)
|
||||||
|
{
|
||||||
|
result.Formatters.Add(new VCardFormatter_V3());
|
||||||
|
result.Formatters.Add(new VCardFormatter_V4());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Produces("text/vcard;VERSION=V3.0")]
|
||||||
|
public Contact ContactInfoUsingV3Format()
|
||||||
|
{
|
||||||
|
return new Contact()
|
||||||
|
{
|
||||||
|
Name = "John Williams",
|
||||||
|
Gender = GenderType.Male
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[Produces("text/vcard;VERSION=V4.0")]
|
||||||
|
public Contact ContactInfoUsingV4Format()
|
||||||
|
{
|
||||||
|
return new Contact()
|
||||||
|
{
|
||||||
|
Name = "John Williams",
|
||||||
|
Gender = GenderType.Male
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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.
|
||||||
|
|
||||||
|
namespace ConnegWebsite.Models
|
||||||
|
{
|
||||||
|
public class Contact
|
||||||
|
{
|
||||||
|
public int ContactId { get; set; }
|
||||||
|
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public GenderType Gender { get; set; }
|
||||||
|
|
||||||
|
public string Address { get; set; }
|
||||||
|
|
||||||
|
public string City { get; set; }
|
||||||
|
|
||||||
|
public string State { get; set; }
|
||||||
|
|
||||||
|
public string Zip { get; set; }
|
||||||
|
|
||||||
|
public string Email { get; set; }
|
||||||
|
|
||||||
|
public string Twitter { get; set; }
|
||||||
|
|
||||||
|
public string Self { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum GenderType
|
||||||
|
{
|
||||||
|
Male,
|
||||||
|
Female
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using ConnegWebsite.Models;
|
||||||
|
using Microsoft.AspNet.Mvc;
|
||||||
|
using Microsoft.AspNet.Mvc.HeaderValueAbstractions;
|
||||||
|
|
||||||
|
namespace ConnegWebsite
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides contact information of a person through VCard format.
|
||||||
|
/// </summary>
|
||||||
|
public class VCardFormatter_V3 : OutputFormatter
|
||||||
|
{
|
||||||
|
public VCardFormatter_V3()
|
||||||
|
{
|
||||||
|
SupportedEncodings.Add(Encoding.UTF8);
|
||||||
|
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/vcard;version=v3.0"));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool CanWriteType(Type declaredType, Type runtimeType)
|
||||||
|
{
|
||||||
|
return typeof(Contact).GetTypeInfo().IsAssignableFrom(runtimeType.GetTypeInfo());
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task WriteResponseBodyAsync(OutputFormatterContext context)
|
||||||
|
{
|
||||||
|
var contact = (Contact)context.Object;
|
||||||
|
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
builder.AppendLine("BEGIN:VCARD");
|
||||||
|
builder.AppendFormat("FN:{0}", contact.Name);
|
||||||
|
builder.AppendLine();
|
||||||
|
builder.AppendLine("END:VCARD");
|
||||||
|
|
||||||
|
var responseStream = new DelegatingStream(context.ActionContext.HttpContext.Response.Body);
|
||||||
|
using (var writer = new StreamWriter(responseStream, context.SelectedEncoding, bufferSize: 1024))
|
||||||
|
{
|
||||||
|
await writer.WriteAsync(builder.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using ConnegWebsite.Models;
|
||||||
|
using Microsoft.AspNet.Mvc;
|
||||||
|
using Microsoft.AspNet.Mvc.HeaderValueAbstractions;
|
||||||
|
|
||||||
|
namespace ConnegWebsite
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides contact information of a person through VCard format.
|
||||||
|
/// In version 4.0 of VCard format, Gender is a supported property.
|
||||||
|
/// </summary>
|
||||||
|
public class VCardFormatter_V4 : OutputFormatter
|
||||||
|
{
|
||||||
|
public VCardFormatter_V4()
|
||||||
|
{
|
||||||
|
SupportedEncodings.Add(Encoding.UTF8);
|
||||||
|
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/vcard;version=v4.0"));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool CanWriteType(Type declaredType, Type runtimeType)
|
||||||
|
{
|
||||||
|
return typeof(Contact).GetTypeInfo().IsAssignableFrom(runtimeType.GetTypeInfo());
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task WriteResponseBodyAsync(OutputFormatterContext context)
|
||||||
|
{
|
||||||
|
var contact = (Contact)context.Object;
|
||||||
|
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
builder.AppendLine("BEGIN:VCARD");
|
||||||
|
builder.AppendFormat("FN:{0}", contact.Name);
|
||||||
|
builder.AppendLine();
|
||||||
|
builder.AppendFormat("GENDER:{0}", (contact.Gender == GenderType.Male) ? "M" : "F");
|
||||||
|
builder.AppendLine();
|
||||||
|
builder.AppendLine("END:VCARD");
|
||||||
|
|
||||||
|
var responseStream = new DelegatingStream(context.ActionContext.HttpContext.Response.Body);
|
||||||
|
using (var writer = new StreamWriter(responseStream, context.SelectedEncoding, bufferSize: 1024))
|
||||||
|
{
|
||||||
|
await writer.WriteAsync(builder.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue