[Fixes #1429] Content negotiation does a case-sensitive matching of media type's parameter values

This commit is contained in:
Kiran Challa 2014-11-05 17:58:04 -08:00
parent 2d32420f01
commit 17e4dd2bf6
8 changed files with 214 additions and 5 deletions

View File

@ -187,7 +187,7 @@ namespace Microsoft.AspNet.Mvc.HeaderValueAbstractions
return false;
}
if (parameterValue2 == null || !parameterValue2.Equals(parameters1[parameterKey]))
if (!string.Equals(parameterValue2, parameters1[parameterKey], StringComparison.OrdinalIgnoreCase))
{
return false;
}

View File

@ -105,6 +105,33 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
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]
public async Task ProducesContentAttribute_OnAction_OverridesTheValueOnClass()
{

View File

@ -103,7 +103,12 @@ namespace Microsoft.AspNet.Mvc.HeaderValueAbstractions
[InlineData("text/plain;", "*/*;charset=utf-8;", true)]
[InlineData("text/plain;", "text/*;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;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", "*/*;charset=utf-8;foo=bar;q=0.0", true)]
[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/*;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,
string mediaType2,
public void MediaTypeHeaderValue_IsSubTypeTests(string mediaType1, // Example: Formatter's supported media type
string mediaType2, // Example: Accept header media type
bool isMediaType1Subset)
{
// 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", "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")]
public void MediaTypeHeaderValue_UpdateValue_RawValueGetsUpdated(string mediaTypeValue,
string expectedRawValue)
{

View File

@ -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">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
@ -11,4 +11,4 @@
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>
</Project>

View File

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

View File

@ -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
}
}

View File

@ -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());
}
}
}
}

View File

@ -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());
}
}
}
}