Skip to content

Commit 86d9b9f

Browse files
Chris Martinezcommonsensesoftware
authored andcommitted
Improve automatic cloning of MediaTypeFormatter. Resolves #156.
1 parent 26ad874 commit 86d9b9f

File tree

6 files changed

+59
-32
lines changed

6 files changed

+59
-32
lines changed

src/Microsoft.AspNet.WebApi.Versioning.ApiExplorer/Description/ApiVersionParameterDescriptionContext.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66
using System.Linq;
77
using System.Net.Http.Formatting;
88
using System.Net.Http.Headers;
9+
using System.Web.Http;
910
using System.Web.Http.Description;
1011
using static Microsoft.Web.Http.Versioning.ApiVersionParameterLocation;
11-
using static System.Web.Http.Description.ApiParameterSource;
1212
using static System.StringComparison;
13-
using System.Web.Http;
13+
using static System.Web.Http.Description.ApiParameterSource;
1414

1515
/// <summary>
1616
/// Represents an object that contains API version parameter descriptions.

src/Microsoft.AspNet.WebApi.Versioning.ApiExplorer/LocalSR.Designer.cs

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Microsoft.AspNet.WebApi.Versioning.ApiExplorer/LocalSR.resx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,6 @@
121121
<value>The requested API version</value>
122122
</data>
123123
<data name="MediaTypeFormatterNotCloneable" xml:space="preserve">
124-
<value>The media type formatter {0} could not be cloned. The type must either implement {1} or define a copy constructor with the signature '{0}({0} formatter)'.</value>
124+
<value>The media type formatter {0} could not be cloned. The type must either implement {1}, define a copy constructor with the signature '{0}({0} formatter)', or have a parameterless constructor.</value>
125125
</data>
126126
</root>

src/Microsoft.AspNet.WebApi.Versioning.ApiExplorer/Microsoft.AspNet.WebApi.Versioning.ApiExplorer.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<VersionPrefix>1.0.0</VersionPrefix>
4+
<VersionPrefix>1.0.1</VersionPrefix>
55
<AssemblyVersion>1.0.0.0</AssemblyVersion>
66
<TargetFramework>net45</TargetFramework>
77
<AssemblyName>Microsoft.AspNet.WebApi.Versioning.ApiExplorer</AssemblyName>
@@ -10,7 +10,7 @@
1010
<RootNamespace>Microsoft.Web.Http</RootNamespace>
1111
<DefineConstants>$(DefineConstants);WEBAPI</DefineConstants>
1212
<PackageTags>Microsoft;AspNet;AspNetWebAPI;Versioning;ApiExplorer</PackageTags>
13-
<PackageReleaseNotes></PackageReleaseNotes>
13+
<PackageReleaseNotes>• Improve cloning of MediaTypeFormatter (Issue #156)</PackageReleaseNotes>
1414
</PropertyGroup>
1515

1616
<ItemGroup>

src/Microsoft.AspNet.WebApi.Versioning.ApiExplorer/System.Web.Http/MediaTypeFormatterAdapterFactory.cs

Lines changed: 41 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,9 @@
77
using System.Diagnostics.Contracts;
88
using System.Linq;
99
using System.Net.Http.Formatting;
10-
using System.Reflection;
11-
using static System.Reflection.BindingFlags;
1210
using static System.Linq.Expressions.Expression;
1311
using static System.Net.Http.Headers.MediaTypeHeaderValue;
12+
using static System.Reflection.BindingFlags;
1413

1514
static class MediaTypeFormatterAdapterFactory
1615
{
@@ -42,21 +41,55 @@ static Func<MediaTypeFormatter, MediaTypeFormatter> NewCloneFunction( Type type
4241
Contract.Requires( type != null );
4342
Contract.Ensures( Contract.Result<Func<MediaTypeFormatter, MediaTypeFormatter>>() != null );
4443

45-
var clone = NewActivator( type );
44+
var clone = NewCopyConstructorActivator( type ) ??
45+
NewParameterlessConstructorActivator( type ) ??
46+
throw new InvalidOperationException( LocalSR.MediaTypeFormatterNotCloneable.FormatDefault( type.Name, typeof( ICloneable ).Name ) );
47+
4648
return instance => CloneMediaTypes( clone( instance ) );
4749
}
4850

49-
static Func<MediaTypeFormatter, MediaTypeFormatter> NewActivator( Type type )
51+
static Func<MediaTypeFormatter, MediaTypeFormatter> NewCopyConstructorActivator( Type type )
52+
{
53+
Contract.Requires( type != null );
54+
55+
var constructors = from ctor in type.GetConstructors( Public | NonPublic | Instance )
56+
let args = ctor.GetParameters()
57+
where args.Length == 1 && type.Equals( args[0].ParameterType )
58+
select ctor;
59+
var constructor = constructors.SingleOrDefault();
60+
61+
if ( constructor == null )
62+
{
63+
return null;
64+
}
65+
66+
var formatter = Parameter( typeof( MediaTypeFormatter ), "formatter" );
67+
var @new = New( constructor, Convert( formatter, type ) );
68+
var lambda = Lambda<Func<MediaTypeFormatter, MediaTypeFormatter>>( @new, formatter );
69+
70+
return lambda.Compile(); // formatter => new MediaTypeFormatter( formatter );
71+
}
72+
73+
static Func<MediaTypeFormatter, MediaTypeFormatter> NewParameterlessConstructorActivator( Type type )
5074
{
5175
Contract.Requires( type != null );
52-
Contract.Ensures( Contract.Result<Func<MediaTypeFormatter, MediaTypeFormatter>>() != null );
5376

54-
var ctor = ResolveConstructor( type );
77+
var constructors = from ctor in type.GetConstructors( Public | NonPublic | Instance )
78+
let args = ctor.GetParameters()
79+
where args.Length == 0
80+
select ctor;
81+
var constructor = constructors.SingleOrDefault();
82+
83+
if ( constructor == null )
84+
{
85+
return null;
86+
}
87+
5588
var formatter = Parameter( typeof( MediaTypeFormatter ), "formatter" );
56-
var @new = New( ctor, Convert( formatter, type ) );
89+
var @new = New( constructor );
5790
var lambda = Lambda<Func<MediaTypeFormatter, MediaTypeFormatter>>( @new, formatter );
5891

59-
return lambda.Compile();
92+
return lambda.Compile(); // formatter => new MediaTypeFormatter();
6093
}
6194

6295
static MediaTypeFormatter CloneMediaTypes( MediaTypeFormatter instance )
@@ -75,21 +108,5 @@ static MediaTypeFormatter CloneMediaTypes( MediaTypeFormatter instance )
75108

76109
return instance;
77110
}
78-
79-
static ConstructorInfo ResolveConstructor( Type type )
80-
{
81-
var constructors = from ctor in type.GetConstructors( Public | NonPublic | Instance )
82-
let args = ctor.GetParameters()
83-
where args.Length == 1 && type.Equals( args[0].ParameterType )
84-
select ctor;
85-
var constructor = constructors.SingleOrDefault();
86-
87-
if ( constructor == null )
88-
{
89-
throw new InvalidOperationException( LocalSR.MediaTypeFormatterNotCloneable.FormatDefault( type.Name, typeof( ICloneable ).Name ) );
90-
}
91-
92-
return constructor;
93-
}
94111
}
95112
}

test/Microsoft.AspNet.WebApi.Versioning.ApiExplorer.Tests/Description/ApiVersionParameterDescriptionContextTest.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,11 +170,13 @@ public void add_parameter_should_add_descriptor_for_media_type_parameter()
170170
// arrange
171171
var configuration = new HttpConfiguration();
172172
var json = new JsonMediaTypeFormatter();
173+
var formUrlEncoded = new FormUrlEncodedMediaTypeFormatter();
173174

174175
configuration.Formatters.Clear();
175176
configuration.Formatters.Add( json );
177+
configuration.Formatters.Add( formUrlEncoded );
176178

177-
var description = new ApiDescription() { SupportedResponseFormatters = { json } };
179+
var description = new ApiDescription() { SupportedRequestBodyFormatters = { json, formUrlEncoded } };
178180
var version = new ApiVersion( 1, 0 );
179181
var options = new ApiExplorerOptions( configuration );
180182
var context = new ApiVersionParameterDescriptionContext( description, version, options );
@@ -183,14 +185,22 @@ public void add_parameter_should_add_descriptor_for_media_type_parameter()
183185
context.AddParameter( "v", MediaTypeParameter );
184186

185187
// assert
186-
var formatter = description.SupportedResponseFormatters.Single();
188+
var formatter = description.SupportedRequestBodyFormatters[0];
187189

188190
foreach ( var mediaType in formatter.SupportedMediaTypes )
189191
{
190192
mediaType.Parameters.Single().Should().Be( new NameValueHeaderValue( "v", "1.0" ) );
191193
}
192194

193195
formatter.Should().NotBeSameAs( json );
196+
formatter = description.SupportedRequestBodyFormatters[1];
197+
198+
foreach ( var mediaType in formatter.SupportedMediaTypes )
199+
{
200+
mediaType.Parameters.Single().Should().Be( new NameValueHeaderValue( "v", "1.0" ) );
201+
}
202+
203+
formatter.Should().NotBeSameAs( formUrlEncoded );
194204
}
195205

196206
[Fact]

0 commit comments

Comments
 (0)