Skip to content

Commit d038f67

Browse files
brainboostChris Martinez
authored andcommitted
Dependency properties should keep custom attributes (#520)
* add suppoting DataMemberAttribute for odata model * dependencies with custom atttributes
1 parent 7f7196e commit d038f67

File tree

6 files changed

+121
-17
lines changed

6 files changed

+121
-17
lines changed
Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,45 @@
11
namespace Microsoft.Examples.Models
22
{
3-
using Microsoft.AspNet.OData.Query;
4-
using System;
3+
using System.Runtime.Serialization;
54

65
/// <summary>
76
/// Represents an address.
87
/// </summary>
8+
[DataContract]
99
public class Address
1010
{
1111
/// <summary>
1212
/// Gets or sets the address identifier.
1313
/// </summary>
14+
[IgnoreDataMember]
1415
public int Id { get; set; }
1516

1617
/// <summary>
1718
/// Gets or sets the street address.
1819
/// </summary>
1920
/// <value>The street address.</value>
21+
[DataMember]
2022
public string Street { get; set; }
2123

2224
/// <summary>
2325
/// Gets or sets the address city.
2426
/// </summary>
2527
/// <value>The address city.</value>
28+
[DataMember]
2629
public string City { get; set; }
2730

2831
/// <summary>
2932
/// Gets or sets the address state.
3033
/// </summary>
3134
/// <value>The address state.</value>
35+
[DataMember]
3236
public string State { get; set; }
3337

3438
/// <summary>
3539
/// Gets or sets the address zip code.
3640
/// </summary>
3741
/// <value>The address zip code.</value>
42+
[DataMember(Name = "zip")]
3843
public string ZipCode { get; set; }
3944
}
4045
}

src/Common.OData.ApiExplorer/AspNet.OData/DefaultModelTypeBuilder.cs

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -96,14 +96,26 @@ static Type GenerateTypeIfNeeded( IEdmStructuredType structuredType, BuilderCont
9696
const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance;
9797

9898
var properties = new List<ClassProperty>();
99-
var structuralProperties = structuredType.Properties().ToDictionary( p => p.Name, StringComparer.OrdinalIgnoreCase );
99+
var structuralProperties = new Dictionary<string, IEdmProperty>( StringComparer.OrdinalIgnoreCase );
100+
var mappedClrProperties = new Dictionary<PropertyInfo, IEdmProperty>();
100101
var clrTypeMatchesEdmType = true;
101102
var hasUnfinishedTypes = false;
102103
var dependentProperties = new List<PropertyDependency>();
103104

105+
foreach ( var property in structuredType.Properties() )
106+
{
107+
structuralProperties.Add( property.Name, property );
108+
var clrProperty = edmModel.GetAnnotationValue<ClrPropertyInfoAnnotation>( property )?.ClrPropertyInfo;
109+
if ( clrProperty != null )
110+
{
111+
mappedClrProperties.Add( clrProperty, property );
112+
}
113+
}
114+
104115
foreach ( var property in clrType.GetProperties( bindingFlags ) )
105116
{
106-
if ( !structuralProperties.TryGetValue( property.Name, out var structuralProperty ) )
117+
if ( !structuralProperties.TryGetValue( property.Name, out var structuralProperty ) &&
118+
!mappedClrProperties.TryGetValue( property, out structuralProperty ) )
107119
{
108120
clrTypeMatchesEdmType = false;
109121
continue;
@@ -129,7 +141,7 @@ static Type GenerateTypeIfNeeded( IEdmStructuredType structuredType, BuilderCont
129141
{
130142
clrTypeMatchesEdmType = false;
131143
hasUnfinishedTypes = true;
132-
dependentProperties.Add( new PropertyDependency( elementKey, true, property.Name ) );
144+
dependentProperties.Add( new PropertyDependency( elementKey, true, property.Name, property.DeclaredAttributes() ) );
133145
continue;
134146
}
135147

@@ -162,7 +174,7 @@ static Type GenerateTypeIfNeeded( IEdmStructuredType structuredType, BuilderCont
162174
{
163175
clrTypeMatchesEdmType = false;
164176
hasUnfinishedTypes = true;
165-
dependentProperties.Add( new PropertyDependency( propertyTypeKey, false, property.Name ) );
177+
dependentProperties.Add( new PropertyDependency( propertyTypeKey, false, property.Name, property.DeclaredAttributes() ) );
166178
continue;
167179
}
168180
}
@@ -232,12 +244,7 @@ static TypeBuilder CreateTypeBuilderFromSignature( ModuleBuilder moduleBuilder,
232244
{
233245
var type = property.Type;
234246
var name = property.Name;
235-
var propertyBuilder = AddProperty( typeBuilder, type, name );
236-
237-
foreach ( var attribute in property.Attributes )
238-
{
239-
propertyBuilder.SetCustomAttribute( attribute );
240-
}
247+
AddProperty( typeBuilder, type, name, property.Attributes );
241248
}
242249

243250
return typeBuilder;
@@ -258,7 +265,7 @@ static IDictionary<EdmTypeKey, TypeInfo> ResolveDependencies( BuilderContext con
258265
dependentOnType = IEnumerableOfT.MakeGenericType( dependentOnType ).GetTypeInfo();
259266
}
260267

261-
AddProperty( propertyDependency.DependentType, dependentOnType, propertyDependency.PropertyName );
268+
AddProperty( propertyDependency.DependentType, dependentOnType, propertyDependency.PropertyName, propertyDependency.CustomAttributes );
262269
}
263270

264271
var keys = edmTypes.Keys.ToArray();
@@ -276,7 +283,7 @@ static IDictionary<EdmTypeKey, TypeInfo> ResolveDependencies( BuilderContext con
276283
return edmTypes;
277284
}
278285

279-
static PropertyBuilder AddProperty( TypeBuilder addTo, Type shouldBeAdded, string name )
286+
static PropertyBuilder AddProperty( TypeBuilder addTo, Type shouldBeAdded, string name, IEnumerable<CustomAttributeBuilder> customAttributes )
280287
{
281288
const MethodAttributes propertyMethodAttributes = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;
282289
var field = addTo.DefineField( "_" + name, shouldBeAdded, FieldAttributes.Private );
@@ -297,6 +304,11 @@ static PropertyBuilder AddProperty( TypeBuilder addTo, Type shouldBeAdded, strin
297304
propertyBuilder.SetGetMethod( getter );
298305
propertyBuilder.SetSetMethod( setter );
299306

307+
foreach ( var attribute in customAttributes )
308+
{
309+
propertyBuilder.SetCustomAttribute( attribute );
310+
}
311+
300312
return propertyBuilder;
301313
}
302314

src/Common.OData.ApiExplorer/AspNet.OData/PropertyDependency.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
namespace Microsoft.AspNet.OData
22
{
3+
using System.Collections.Generic;
34
using System.Reflection.Emit;
45

56
/// <summary>
@@ -13,13 +14,15 @@ internal class PropertyDependency
1314
/// <param name="dependentOnTypeKey">The key of the type the property has a dependency on.</param>
1415
/// <param name="propertyName">The name of the property.</param>
1516
/// <param name="isCollection">Whether the property is a collection or not.</param>
16-
internal PropertyDependency( EdmTypeKey dependentOnTypeKey, bool isCollection, string propertyName )
17+
/// <param name="customAttributes">A collection of custom attribute builders.</param>
18+
internal PropertyDependency( EdmTypeKey dependentOnTypeKey, bool isCollection, string propertyName, IEnumerable<CustomAttributeBuilder> customAttributes )
1719
{
1820
Arg.NotNull<EdmTypeKey>( dependentOnTypeKey, nameof( dependentOnTypeKey ) );
1921
Arg.NotNull<string>( propertyName, nameof( propertyName ) );
2022

2123
DependentOnTypeKey = dependentOnTypeKey;
2224
PropertyName = propertyName;
25+
CustomAttributes = customAttributes;
2326
IsCollection = isCollection;
2427
}
2528

@@ -42,5 +45,10 @@ internal PropertyDependency( EdmTypeKey dependentOnTypeKey, bool isCollection,
4245
/// Gets a value indicating whether the property is a collection.
4346
/// </summary>
4447
internal bool IsCollection { get; }
48+
49+
/// <summary>
50+
/// Gets custom attribute builders of the property.
51+
/// </summary>
52+
internal IEnumerable<CustomAttributeBuilder> CustomAttributes { get; }
4553
}
4654
}
Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,27 @@
11
namespace Microsoft.AspNet.OData
22
{
33
using System.Collections.Generic;
4+
using System.Runtime.Serialization;
45

6+
[DataContract]
57
public class Contact
68
{
9+
[DataMember]
710
public int ContactId { get; set; }
811

12+
[DataMember( Name = "first_name" )]
913
public string FirstName { get; set; }
1014

15+
[DataMember]
1116
public string LastName { get; set; }
1217

13-
public string Email { get; set; }
18+
[DataMember]
19+
public Email Email { get; set; }
1420

21+
[DataMember( Name = "telephone" )]
1522
public string Phone { get; set; }
1623

24+
[DataMember]
1725
public List<Address> Addresses { get; set; }
1826
}
1927
}

test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/DefaultModelTypeBuilderTest.cs

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
namespace Microsoft.AspNet.OData
22
{
33
using FluentAssertions;
4+
using FluentAssertions.Common;
5+
using System.Runtime.Serialization;
46
using Microsoft.AspNet.OData.Builder;
57
using Microsoft.AspNetCore.Mvc;
68
using Microsoft.Extensions.DependencyInjection;
@@ -207,7 +209,7 @@ public void type_should_match_edm_with_child_entity_substitution( Type originalT
207209
nextType.Should().HaveProperty<int>( nameof( Contact.ContactId ) );
208210
nextType.Should().HaveProperty<string>( nameof( Contact.FirstName ) );
209211
nextType.Should().HaveProperty<string>( nameof( Contact.LastName ) );
210-
nextType.Should().HaveProperty<string>( nameof( Contact.Email ) );
212+
nextType.Should().HaveProperty<Email>( nameof( Contact.Email ) );
211213
nextType.Should().HaveProperty<string>( nameof( Contact.Phone ) );
212214
nextType = nextType.GetRuntimeProperty( nameof( Contact.Addresses ) ).PropertyType.GetGenericArguments()[0];
213215
nextType.GetRuntimeProperties().Should().HaveCount( 5 );
@@ -408,6 +410,61 @@ public void substitute_should_resolve_types_that_reference_a_model_that_match_th
408410
substitutionType.Should().NotBeOfType<TypeBuilder>();
409411
}
410412

413+
[Fact]
414+
public void substituted_type_should_have_renamed_with_attribute_properties_from_original_type()
415+
{
416+
// arrange
417+
var modelBuilder = new ODataConventionModelBuilder();
418+
modelBuilder.EntitySet<Contact>( "Contacts" );
419+
420+
var context = NewContext( modelBuilder.GetEdmModel() );
421+
var originalType = typeof( Contact );
422+
423+
// act
424+
var substitutedType = originalType.SubstituteIfNecessary( context );
425+
426+
// assert
427+
substitutedType.Should().HaveProperty<string>( nameof( Contact.FirstName ) );
428+
substitutedType.GetRuntimeProperty( nameof( Contact.FirstName ) ).Should().NotBeNull();
429+
substitutedType.GetRuntimeProperty( nameof( Contact.FirstName ) ).HasAttribute<DataMemberAttribute>();
430+
}
431+
432+
[Fact]
433+
public void substituted_type_should_keep_custom_attributes_on_dependency_property()
434+
{
435+
// arrange
436+
var modelBuilder = new ODataConventionModelBuilder();
437+
modelBuilder.EntitySet<Contact>( "Contacts" );
438+
439+
var context = NewContext( modelBuilder.GetEdmModel() );
440+
var originalType = typeof( Contact );
441+
442+
// act
443+
var substitutedType = originalType.SubstituteIfNecessary( context );
444+
445+
// assert
446+
substitutedType.GetRuntimeProperty( nameof( Contact.Email ) ).Should().NotBeNull();
447+
substitutedType.GetRuntimeProperty( nameof( Contact.Email ) ).HasAttribute<DataMemberAttribute>();
448+
}
449+
450+
[Fact]
451+
public void substituted_type_should_keep_custom_attributes_on_collection_dependency_property()
452+
{
453+
// arrange
454+
var modelBuilder = new ODataConventionModelBuilder();
455+
modelBuilder.EntitySet<Contact>( "Contacts" );
456+
457+
var context = NewContext( modelBuilder.GetEdmModel() );
458+
var originalType = typeof( Contact );
459+
460+
// act
461+
var substitutedType = originalType.SubstituteIfNecessary( context );
462+
463+
// assert
464+
substitutedType.GetRuntimeProperty( nameof( Contact.Addresses ) ).Should().NotBeNull();
465+
substitutedType.GetRuntimeProperty( nameof( Contact.Addresses ) ).HasAttribute<DataMemberAttribute>();
466+
}
467+
411468
public static IEnumerable<object[]> SubstitutionNotRequiredData
412469
{
413470
get
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
namespace Microsoft.AspNet.OData
2+
{
3+
using System.Runtime.Serialization;
4+
5+
[DataContract]
6+
public class Email
7+
{
8+
[DataMember]
9+
public string Server { get; set; }
10+
11+
[DataMember]
12+
public string Username { get; set; }
13+
}
14+
}

0 commit comments

Comments
 (0)