diff --git a/src/Microsoft.AspNet.Mvc.HeaderValueAbstractions/MediaTypeHeaderValue.cs b/src/Microsoft.AspNet.Mvc.HeaderValueAbstractions/MediaTypeHeaderValue.cs index c8011b52a4..a9b363a255 100644 --- a/src/Microsoft.AspNet.Mvc.HeaderValueAbstractions/MediaTypeHeaderValue.cs +++ b/src/Microsoft.AspNet.Mvc.HeaderValueAbstractions/MediaTypeHeaderValue.cs @@ -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; } diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/ConnegTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/ConnegTests.cs index b955261d07..28e152c4f3 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/ConnegTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/ConnegTests.cs @@ -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() { diff --git a/test/Microsoft.AspNet.Mvc.HeaderValueAbstractions.Test/MediaTypeHeaderValueParsingTests.cs b/test/Microsoft.AspNet.Mvc.HeaderValueAbstractions.Test/MediaTypeHeaderValueParsingTests.cs index 4e138a5725..f3b199835d 100644 --- a/test/Microsoft.AspNet.Mvc.HeaderValueAbstractions.Test/MediaTypeHeaderValueParsingTests.cs +++ b/test/Microsoft.AspNet.Mvc.HeaderValueAbstractions.Test/MediaTypeHeaderValueParsingTests.cs @@ -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) { diff --git a/test/WebSites/ConnegWebSite/ConnegWebsite.kproj b/test/WebSites/ConnegWebSite/ConnegWebsite.kproj index 5ecfd616de..7f20e114b8 100644 --- a/test/WebSites/ConnegWebSite/ConnegWebsite.kproj +++ b/test/WebSites/ConnegWebSite/ConnegWebsite.kproj @@ -1,4 +1,4 @@ - + 14.0 @@ -11,4 +11,4 @@ ..\..\..\artifacts\bin\$(MSBuildProjectName)\ - + \ No newline at end of file diff --git a/test/WebSites/ConnegWebSite/Controllers/ProducesWithMediaTypeParametersController.cs b/test/WebSites/ConnegWebSite/Controllers/ProducesWithMediaTypeParametersController.cs new file mode 100644 index 0000000000..e87db2ffa9 --- /dev/null +++ b/test/WebSites/ConnegWebSite/Controllers/ProducesWithMediaTypeParametersController.cs @@ -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 + }; + } + } +} \ No newline at end of file diff --git a/test/WebSites/ConnegWebSite/Models/Contact.cs b/test/WebSites/ConnegWebSite/Models/Contact.cs new file mode 100644 index 0000000000..287eb5e444 --- /dev/null +++ b/test/WebSites/ConnegWebSite/Models/Contact.cs @@ -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 + } +} \ No newline at end of file diff --git a/test/WebSites/ConnegWebSite/VCardFormatter_V3.cs b/test/WebSites/ConnegWebSite/VCardFormatter_V3.cs new file mode 100644 index 0000000000..fbcb6af7f2 --- /dev/null +++ b/test/WebSites/ConnegWebSite/VCardFormatter_V3.cs @@ -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 +{ + /// + /// Provides contact information of a person through VCard format. + /// + 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()); + } + } + } +} \ No newline at end of file diff --git a/test/WebSites/ConnegWebSite/VCardFormatter_V4.cs b/test/WebSites/ConnegWebSite/VCardFormatter_V4.cs new file mode 100644 index 0000000000..a77f02a176 --- /dev/null +++ b/test/WebSites/ConnegWebSite/VCardFormatter_V4.cs @@ -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 +{ + /// + /// Provides contact information of a person through VCard format. + /// In version 4.0 of VCard format, Gender is a supported property. + /// + 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()); + } + } + } +} \ No newline at end of file