From 55f3f311202ca3d3ec2438109f5ee61da7cca088 Mon Sep 17 00:00:00 2001 From: Sergei Vedishchev Date: Fri, 5 Jul 2019 14:48:52 +0200 Subject: [PATCH 1/9] add suppoting DataMemberAttribute for odata model --- .../AspNet.OData/DefaultModelTypeBuilder.cs | 13 +++++++++++-- ...t.AspNetCore.OData.Versioning.ApiExplorer.csproj | 4 +++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/Common.OData.ApiExplorer/AspNet.OData/DefaultModelTypeBuilder.cs b/src/Common.OData.ApiExplorer/AspNet.OData/DefaultModelTypeBuilder.cs index 9bef7319..cf4fe019 100644 --- a/src/Common.OData.ApiExplorer/AspNet.OData/DefaultModelTypeBuilder.cs +++ b/src/Common.OData.ApiExplorer/AspNet.OData/DefaultModelTypeBuilder.cs @@ -105,8 +105,12 @@ static Type GenerateTypeIfNeeded( IEdmStructuredType structuredType, BuilderCont { if ( !structuralProperties.TryGetValue( property.Name, out var structuralProperty ) ) { - clrTypeMatchesEdmType = false; - continue; + var name = GetNameFromAttribute( property ); + if ( string.IsNullOrEmpty( name ) || !structuralProperties.TryGetValue( name, out structuralProperty ) ) + { + clrTypeMatchesEdmType = false; + continue; + } } var structuredTypeRef = structuralProperty.Type; @@ -311,6 +315,11 @@ static ModuleBuilder CreateModuleForApiVersion( ApiVersion apiVersion ) return assemblyBuilder.DefineDynamicModule( "" ); } + static string GetNameFromAttribute( PropertyInfo property ) + { + return property.GetCustomAttribute()?.Name; + } + sealed class BuilderContext { readonly Lazy moduleBuilder; diff --git a/src/Microsoft.AspNetCore.OData.Versioning.ApiExplorer/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.csproj b/src/Microsoft.AspNetCore.OData.Versioning.ApiExplorer/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.csproj index 7c785ead..024f29c5 100644 --- a/src/Microsoft.AspNetCore.OData.Versioning.ApiExplorer/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.csproj +++ b/src/Microsoft.AspNetCore.OData.Versioning.ApiExplorer/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.csproj @@ -2,13 +2,15 @@ 3.2.0 - 3.2.0.0 + 3.2.1.0 netstandard2.0 Microsoft ASP.NET Core Versioned API Explorer for OData v4.0 The API Explorer for Microsoft ASP.NET Core and OData v4.0. Microsoft $(DefineConstants);API_EXPLORER Microsoft;AspNet;AspNetCore;OData;Versioning;ApiExplorer + 3.2.1 + true From c62b22b52986ea4299041cb4d64d251664498e0b Mon Sep 17 00:00:00 2001 From: Sergei Vedishchev Date: Sun, 7 Jul 2019 21:58:35 +0200 Subject: [PATCH 2/9] unit test case added --- .../AspNet.OData/DefaultModelTypeBuilder.cs | 2 +- .../AspNet.OData/Contact.cs | 13 ++++++++++--- .../DefaultModelTypeBuilderTest.cs | 18 ++++++++++++++++++ 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/Common.OData.ApiExplorer/AspNet.OData/DefaultModelTypeBuilder.cs b/src/Common.OData.ApiExplorer/AspNet.OData/DefaultModelTypeBuilder.cs index cf4fe019..e7e5463c 100644 --- a/src/Common.OData.ApiExplorer/AspNet.OData/DefaultModelTypeBuilder.cs +++ b/src/Common.OData.ApiExplorer/AspNet.OData/DefaultModelTypeBuilder.cs @@ -315,7 +315,7 @@ static ModuleBuilder CreateModuleForApiVersion( ApiVersion apiVersion ) return assemblyBuilder.DefineDynamicModule( "" ); } - static string GetNameFromAttribute( PropertyInfo property ) + static string GetNameFromAttribute( MemberInfo property ) { return property.GetCustomAttribute()?.Name; } diff --git a/test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/Contact.cs b/test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/Contact.cs index 6f4a1ad5..6f27bdff 100644 --- a/test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/Contact.cs +++ b/test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/Contact.cs @@ -1,18 +1,25 @@ -namespace Microsoft.AspNet.OData +using System.Runtime.Serialization; + +namespace Microsoft.AspNet.OData { using System.Collections.Generic; - + [DataContract] public class Contact { + [DataMember] public int ContactId { get; set; } - + [DataMember(Name = "first_name")] public string FirstName { get; set; } + [DataMember] public string LastName { get; set; } + [DataMember] public string Email { get; set; } + [DataMember] public string Phone { get; set; } + [DataMember] public List
Addresses { get; set; } } diff --git a/test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/DefaultModelTypeBuilderTest.cs b/test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/DefaultModelTypeBuilderTest.cs index 315af1a1..23ec5b44 100644 --- a/test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/DefaultModelTypeBuilderTest.cs +++ b/test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/DefaultModelTypeBuilderTest.cs @@ -408,6 +408,24 @@ public void substitute_should_resolve_types_that_reference_a_model_that_match_th substitutionType.Should().NotBeOfType(); } + [Fact] + public void substituted_type_should_have_renamed_with_attribute_properties_from_original_type() + { + // arrange + var modelBuilder = new ODataConventionModelBuilder(); + modelBuilder.EntitySet( "Contacts" ); + + var context = NewContext( modelBuilder.GetEdmModel() ); + var originalType = typeof( Contact ); + + // act + var substitutedType = originalType.SubstituteIfNecessary( context ); + + // assert + substitutedType.Should().HaveProperty(nameof( Contact.FirstName ) ); + } + + public static IEnumerable SubstitutionNotRequiredData { get From dcf036d941cc6efd3a116251ac105681db4c4f4a Mon Sep 17 00:00:00 2001 From: Sergei Vedishchev Date: Mon, 8 Jul 2019 15:42:24 +0200 Subject: [PATCH 3/9] revert unwanted version change --- .../Microsoft.AspNetCore.OData.Versioning.ApiExplorer.csproj | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Microsoft.AspNetCore.OData.Versioning.ApiExplorer/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.csproj b/src/Microsoft.AspNetCore.OData.Versioning.ApiExplorer/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.csproj index 024f29c5..7c785ead 100644 --- a/src/Microsoft.AspNetCore.OData.Versioning.ApiExplorer/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.csproj +++ b/src/Microsoft.AspNetCore.OData.Versioning.ApiExplorer/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.csproj @@ -2,15 +2,13 @@ 3.2.0 - 3.2.1.0 + 3.2.0.0 netstandard2.0 Microsoft ASP.NET Core Versioned API Explorer for OData v4.0 The API Explorer for Microsoft ASP.NET Core and OData v4.0. Microsoft $(DefineConstants);API_EXPLORER Microsoft;AspNet;AspNetCore;OData;Versioning;ApiExplorer - 3.2.1 - true From 964d5397bb64a46fea6faf61e37f1d4ed2f9e604 Mon Sep 17 00:00:00 2001 From: Sergei Vedishchev Date: Mon, 15 Jul 2019 19:16:30 +0200 Subject: [PATCH 4/9] added v4 to examples --- .../Configuration/ApiVersions.cs | 1 + .../Configuration/OrderModelConfiguration.cs | 2 + .../Configuration/PersonModelConfiguration.cs | 4 + .../Configuration/Ten99ModelConfiguration.cs | 78 +++++++++++++++++++ .../Models/AccountDetails.cs | 20 +++++ .../SwaggerODataSample/Models/Address.cs | 14 ++++ .../SwaggerODataSample/Models/Ten99BModel.cs | 43 ++++++++++ .../Models/Ten99DivModel.cs | 35 +++++++++ .../SwaggerODataSample/Models/TrustModel.cs | 34 ++++++++ .../SwaggerODataSample.csproj | 1 + .../SwaggerODataSample/V4/Ten99BController.cs | 41 ++++++++++ .../V4/Ten99DivController.cs | 39 ++++++++++ .../aspnetcore/SwaggerODataSample/web.config | 13 ++-- .../AspNet.OData/MethodCommentAttribute.cs | 14 ++++ 14 files changed, 334 insertions(+), 5 deletions(-) create mode 100644 samples/aspnetcore/SwaggerODataSample/Configuration/Ten99ModelConfiguration.cs create mode 100644 samples/aspnetcore/SwaggerODataSample/Models/AccountDetails.cs create mode 100644 samples/aspnetcore/SwaggerODataSample/Models/Ten99BModel.cs create mode 100644 samples/aspnetcore/SwaggerODataSample/Models/Ten99DivModel.cs create mode 100644 samples/aspnetcore/SwaggerODataSample/Models/TrustModel.cs create mode 100644 samples/aspnetcore/SwaggerODataSample/V4/Ten99BController.cs create mode 100644 samples/aspnetcore/SwaggerODataSample/V4/Ten99DivController.cs create mode 100644 test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/MethodCommentAttribute.cs diff --git a/samples/aspnetcore/SwaggerODataSample/Configuration/ApiVersions.cs b/samples/aspnetcore/SwaggerODataSample/Configuration/ApiVersions.cs index 527596f1..23ac96e2 100644 --- a/samples/aspnetcore/SwaggerODataSample/Configuration/ApiVersions.cs +++ b/samples/aspnetcore/SwaggerODataSample/Configuration/ApiVersions.cs @@ -7,5 +7,6 @@ static class ApiVersions internal static readonly ApiVersion V1 = new ApiVersion( 1, 0 ); internal static readonly ApiVersion V2 = new ApiVersion( 2, 0 ); internal static readonly ApiVersion V3 = new ApiVersion( 3, 0 ); + internal static readonly ApiVersion V4 = new ApiVersion( 4, 0 ); } } \ No newline at end of file diff --git a/samples/aspnetcore/SwaggerODataSample/Configuration/OrderModelConfiguration.cs b/samples/aspnetcore/SwaggerODataSample/Configuration/OrderModelConfiguration.cs index 652dd3ee..d206198b 100644 --- a/samples/aspnetcore/SwaggerODataSample/Configuration/OrderModelConfiguration.cs +++ b/samples/aspnetcore/SwaggerODataSample/Configuration/OrderModelConfiguration.cs @@ -16,6 +16,8 @@ public class OrderModelConfiguration : IModelConfiguration /// The API version associated with the . public void Apply( ODataModelBuilder builder, ApiVersion apiVersion ) { + if ( apiVersion >= ApiVersions.V4 ) return; + var order = builder.EntitySet( "Orders" ).EntityType.HasKey( o => o.Id ); var lineItem = builder.EntityType().HasKey( li => li.Number ); diff --git a/samples/aspnetcore/SwaggerODataSample/Configuration/PersonModelConfiguration.cs b/samples/aspnetcore/SwaggerODataSample/Configuration/PersonModelConfiguration.cs index c2c55bfe..2e418856 100644 --- a/samples/aspnetcore/SwaggerODataSample/Configuration/PersonModelConfiguration.cs +++ b/samples/aspnetcore/SwaggerODataSample/Configuration/PersonModelConfiguration.cs @@ -17,8 +17,12 @@ public class PersonModelConfiguration : IModelConfiguration /// The API version associated with the . public void Apply( ODataModelBuilder builder, ApiVersion apiVersion ) { + if ( apiVersion >= ApiVersions.V4 ) return; var person = builder.EntitySet( "People" ).EntityType; var address = builder.EntityType
().HasKey( a => a.Id ); + var subAddress = builder.EntityType().HasKey( a => a.Id ); + address.ContainsOptional( p => p.SubAddress ); + subAddress.ContainsOptional( p => p.InnerAddress ); person.HasKey( p => p.Id ); person.Select().OrderBy( "firstName", "lastName" ); diff --git a/samples/aspnetcore/SwaggerODataSample/Configuration/Ten99ModelConfiguration.cs b/samples/aspnetcore/SwaggerODataSample/Configuration/Ten99ModelConfiguration.cs new file mode 100644 index 00000000..c7ce15d8 --- /dev/null +++ b/samples/aspnetcore/SwaggerODataSample/Configuration/Ten99ModelConfiguration.cs @@ -0,0 +1,78 @@ +using Microsoft.AspNet.OData.Builder; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Examples.Models; + +namespace Microsoft.Examples.Configuration +{ + public class Ten99ModelConfiguration : IModelConfiguration + { + /// + /// Applies model configurations using the provided builder for the specified API version. + /// + /// The builder used to apply configurations. + /// The API version associated with the . + public void Apply( ODataModelBuilder builder, ApiVersion apiVersion ) + { + if ( apiVersion < ApiVersions.V4 ) + { + return; + } + + var trust = builder.EntityType();//.HasKey( e => e.TrustKey ); + trust.HasKey( e => new { e.TrustKey, e.YrSeq, e.PermSeq } ); + //trust.Name = "Account"; + + var trustPerm = builder.EntityType();//.HasKey( e => e.TrustKey ); + trustPerm.HasKey( e => new { e.TrustKey, e.PermSeq } ); + //trustPerm.Name = "AccountDetails"; + + trust.ContainsOptional( t => t.Details );//.Name = "account_details"; + //trust.Ignore( t => t.TrustKey ); + //trust.Ignore( t => t.PermSeq ); + //trust.Property( t => t.YrSeq ).Name = "yr_seq"; + + + //trustPerm.Ignore( t => t.TrustKey ); + //trustPerm.Ignore( t => t.PermSeq ); + //trustPerm.Filter() + // .Count() + // .Expand() + // .OrderBy() + // .Page() + // .Select(); + + var ten99b = builder.EntitySet( "Ten99B" ).EntityType; + ten99b.HasKey( o => new { o.YrSeq, o.Pan } ); + //ten99b.HasKey( o => o.YrSeq ); + //ten99b.HasKey( o => o.Pan ); + + ten99b.ContainsOptional( t => t.Account ); + //ten99b.Name = "account"; + //ten99b.Ignore( t => t.TrustKey ); + //ten99b.Property( t => t.YrSeq ).Name = "yr_seq"; + //ten99b.Filter() + // .Count() + // .Expand() + // .OrderBy() + // .Page() + // .Select(); + + var ten99div = builder.EntitySet( "Ten99Div" ).EntityType; + ten99div.HasKey( o => new { o.YrSeq, o.Pan } ); + //ten99div.HasKey( o => o.YrSeq ); + //ten99div.HasKey( o => o.Pan ); + + + //ten99div.Ignore( t => t.TrustKey ); + //ten99div.Property( t => t.YrSeq ).Name = "yr_seq"; + ten99div.ContainsOptional( t => t.Account );//.Name = "account"; + //ten99div.Filter() + // .Count() + // .Expand() + // .OrderBy() + // .Page() + // .Select(); + + } + } +} \ No newline at end of file diff --git a/samples/aspnetcore/SwaggerODataSample/Models/AccountDetails.cs b/samples/aspnetcore/SwaggerODataSample/Models/AccountDetails.cs new file mode 100644 index 00000000..4f36d450 --- /dev/null +++ b/samples/aspnetcore/SwaggerODataSample/Models/AccountDetails.cs @@ -0,0 +1,20 @@ +using System.Runtime.Serialization; +using Microsoft.AspNet.OData.Query; + +namespace Microsoft.Examples.Models +{ + //[Select] + [DataContract] + public class AccountDetails + { + [DataMember] + public int TrustKey { get; set; } + [DataMember] + public byte PermSeq { get; set; } + /// + /// Taxpayer name + /// + [DataMember( Name = "name_1" )] + public string Name1 { get; set; } + } +} \ No newline at end of file diff --git a/samples/aspnetcore/SwaggerODataSample/Models/Address.cs b/samples/aspnetcore/SwaggerODataSample/Models/Address.cs index 70e684e9..5459e26a 100644 --- a/samples/aspnetcore/SwaggerODataSample/Models/Address.cs +++ b/samples/aspnetcore/SwaggerODataSample/Models/Address.cs @@ -36,5 +36,19 @@ public class Address /// /// The address zip code. public string ZipCode { get; set; } + /// + /// Gets or sets the sub address. + /// + public SubAddress SubAddress { get; set; } + + } + + public class SubAddress + { + public int Id { get; set; } + /// + /// Person inner adderrss + /// + public Address InnerAddress { get; set; } } } \ No newline at end of file diff --git a/samples/aspnetcore/SwaggerODataSample/Models/Ten99BModel.cs b/samples/aspnetcore/SwaggerODataSample/Models/Ten99BModel.cs new file mode 100644 index 00000000..b034d605 --- /dev/null +++ b/samples/aspnetcore/SwaggerODataSample/Models/Ten99BModel.cs @@ -0,0 +1,43 @@ +using System.Runtime.Serialization; +using Microsoft.AspNet.OData.Builder; + +namespace Microsoft.Examples.Models +{ + [DataContract] + public class Ten99BModel + { + /// + /// Product Access Number + /// + [DataMember( Name = "pan" )] + public string Pan { get; set; } + + /// + /// Account number + /// + [DataMember( Name = "account_number" )] + //[DataMember] + public string TrustNo { get; set; } + /// + /// Key + /// + [DataMember( Name = "key_trust")] + public int TrustKey { get; set; } + + /// + /// Tax year and sequence + /// + [DataMember( Name = "yr_seq" )] + //[DataMember] + public short YrSeq { get; set; } + + /// + /// Account + /// + [DataMember( Name = "account_trust" )] + //[DataMember] + //[Contained] + public TrustModel Account { get; set; } + + } +} \ No newline at end of file diff --git a/samples/aspnetcore/SwaggerODataSample/Models/Ten99DivModel.cs b/samples/aspnetcore/SwaggerODataSample/Models/Ten99DivModel.cs new file mode 100644 index 00000000..3523db47 --- /dev/null +++ b/samples/aspnetcore/SwaggerODataSample/Models/Ten99DivModel.cs @@ -0,0 +1,35 @@ +using System.Runtime.Serialization; + +namespace Microsoft.Examples.Models +{ + [DataContract] + public class Ten99DivModel + { + /// + /// Product Access Number + /// + [DataMember( Name = "pan" )] + public string Pan { get; set; } + /// + /// Account number + /// + [DataMember( Name = "account_number" )] + public string TrustNo { get; set; } + /// + /// Key + /// + [DataMember] + public int TrustKey { get; set; } + /// + /// Tax year and sequence + /// + [DataMember( Name = "yr_seq" )] + public short YrSeq { get; set; } + /// + /// Account + /// + [DataMember( Name = "account" )] + public TrustModel Account { get; set; } + + } +} \ No newline at end of file diff --git a/samples/aspnetcore/SwaggerODataSample/Models/TrustModel.cs b/samples/aspnetcore/SwaggerODataSample/Models/TrustModel.cs new file mode 100644 index 00000000..a67b9855 --- /dev/null +++ b/samples/aspnetcore/SwaggerODataSample/Models/TrustModel.cs @@ -0,0 +1,34 @@ +using System.Runtime.Serialization; +using Microsoft.AspNet.OData.Builder; +using Microsoft.AspNet.OData.Query; + +namespace Microsoft.Examples.Models +{ + //[Select] + [DataContract] + public class TrustModel + { + /// + /// Key + /// + [DataMember] + public int TrustKey { get; set; } + /// + /// Perm sequence + /// + [DataMember] + public byte PermSeq { get; set; } + /// + /// Tax year and sequence + /// + [DataMember( Name = "yr_seq" )] + public short YrSeq { get; set; } + /// + /// Account details + /// + [DataMember( Name = "account_details" )] + //[DataMember] + //[Contained] + public AccountDetails Details { get; set; } + } +} \ No newline at end of file diff --git a/samples/aspnetcore/SwaggerODataSample/SwaggerODataSample.csproj b/samples/aspnetcore/SwaggerODataSample/SwaggerODataSample.csproj index eed4cc1d..abb88ca4 100644 --- a/samples/aspnetcore/SwaggerODataSample/SwaggerODataSample.csproj +++ b/samples/aspnetcore/SwaggerODataSample/SwaggerODataSample.csproj @@ -11,6 +11,7 @@ + diff --git a/samples/aspnetcore/SwaggerODataSample/V4/Ten99BController.cs b/samples/aspnetcore/SwaggerODataSample/V4/Ten99BController.cs new file mode 100644 index 00000000..cb6bb812 --- /dev/null +++ b/samples/aspnetcore/SwaggerODataSample/V4/Ten99BController.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNet.OData; +using Microsoft.AspNet.OData.Routing; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Examples.Models; +using static Microsoft.AspNet.OData.Query.AllowedQueryOptions; +using static Microsoft.AspNetCore.Http.StatusCodes; + +namespace Microsoft.Examples.V4 +{ + [ApiVersion( "4.0" )] + [ODataRoutePrefix( "Ten99B" )] + public class Ten99BController : ODataController + { + /// + /// Retrieves all orders. + /// + /// Tax year + /// PAN + /// All available orders. + /// Orders successfully retrieved. + /// The order is invalid. + [ODataRoute( "({year},{pan})" )] + [Produces( "application/json" )] + [ProducesResponseType( typeof( IQueryable ), Status200OK )] + [EnableQuery( MaxTop = 100, AllowedQueryOptions = Select | Top | Skip | Count )] + public IQueryable Get( [FromODataUri] short year, [FromODataUri] string pan ) + { + var orders = new[] + { + new Ten99BModel {Pan = pan, TrustNo = "1", YrSeq = year, Account = new TrustModel {YrSeq = year, Details = new AccountDetails { Name1 = pan }}}, + new Ten99BModel {Pan = pan, TrustNo = "2", YrSeq = year, Account = new TrustModel {YrSeq = year, Details = new AccountDetails { Name1 = pan }}}, + new Ten99BModel {Pan = pan, TrustNo = "3", YrSeq = year, Account = new TrustModel {YrSeq = year, Details = new AccountDetails { Name1 = pan }}} + }; + + return orders.AsQueryable(); + } + } +} \ No newline at end of file diff --git a/samples/aspnetcore/SwaggerODataSample/V4/Ten99DivController.cs b/samples/aspnetcore/SwaggerODataSample/V4/Ten99DivController.cs new file mode 100644 index 00000000..b51cfe09 --- /dev/null +++ b/samples/aspnetcore/SwaggerODataSample/V4/Ten99DivController.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNet.OData; +using Microsoft.AspNet.OData.Query; +using Microsoft.AspNet.OData.Routing; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Examples.Models; + +namespace Microsoft.Examples.V4 +{ + [ApiVersion( "4.0" )] + [ODataRoutePrefix( "Ten99Div" )] + public class Ten99DivController : ODataController + { + /// + /// Retrieves all orders. + /// + /// All available orders. + /// Orders successfully retrieved. + /// The order is invalid. + [ODataRoute( "({year},{pan})" )] + [ProducesResponseType( typeof( IQueryable ), StatusCodes.Status200OK )] + [EnableQuery( MaxTop = 100, AllowedQueryOptions = AllowedQueryOptions.Select | AllowedQueryOptions.Top | AllowedQueryOptions.Skip | AllowedQueryOptions.Count )] + [Produces( "application/json" )] + public IQueryable Get( [FromODataUri] short year, [FromODataUri] string pan ) + { + var orders = new[] + { + new Ten99DivModel {Pan = pan, TrustNo = "1", YrSeq = year, Account = new TrustModel {YrSeq = year, Details = new AccountDetails {Name1 = pan}}}, + new Ten99DivModel {Pan = pan, TrustNo = "2", YrSeq = year, Account = new TrustModel {YrSeq = year, Details = new AccountDetails {Name1 = pan}}}, + new Ten99DivModel {Pan = pan, TrustNo = "3", YrSeq = year, Account = new TrustModel {YrSeq = year, Details = new AccountDetails {Name1 = pan}}} + }; + + return orders.AsQueryable(); + } + + } +} \ No newline at end of file diff --git a/samples/aspnetcore/SwaggerODataSample/web.config b/samples/aspnetcore/SwaggerODataSample/web.config index dc0514fc..a2cf1fe2 100644 --- a/samples/aspnetcore/SwaggerODataSample/web.config +++ b/samples/aspnetcore/SwaggerODataSample/web.config @@ -1,14 +1,17 @@  - - - + - + + + + + + - + \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/MethodCommentAttribute.cs b/test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/MethodCommentAttribute.cs new file mode 100644 index 00000000..370e951e --- /dev/null +++ b/test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/MethodCommentAttribute.cs @@ -0,0 +1,14 @@ +using System; + +namespace Microsoft.AspNet.OData +{ + [AttributeUsage( AttributeTargets.Method )] + public class MethodCommentAttribute : Attribute + { + public MethodCommentAttribute(string comment) + { + Comment = comment; + } + public string Comment { get; } + } +} \ No newline at end of file From 1bf445b557fe32a4d35650986f069f8a1cbe3821 Mon Sep 17 00:00:00 2001 From: Sergei Vedishchev Date: Mon, 15 Jul 2019 21:50:48 +0200 Subject: [PATCH 5/9] resolving review comments --- .../AspNet.OData/DefaultModelTypeBuilder.cs | 29 ++++++++++--------- .../AspNet.OData/Contact.cs | 16 +++++----- .../DefaultModelTypeBuilderTest.cs | 2 +- 3 files changed, 26 insertions(+), 21 deletions(-) diff --git a/src/Common.OData.ApiExplorer/AspNet.OData/DefaultModelTypeBuilder.cs b/src/Common.OData.ApiExplorer/AspNet.OData/DefaultModelTypeBuilder.cs index e7e5463c..2dddb4a3 100644 --- a/src/Common.OData.ApiExplorer/AspNet.OData/DefaultModelTypeBuilder.cs +++ b/src/Common.OData.ApiExplorer/AspNet.OData/DefaultModelTypeBuilder.cs @@ -96,21 +96,29 @@ static Type GenerateTypeIfNeeded( IEdmStructuredType structuredType, BuilderCont const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance; var properties = new List(); - var structuralProperties = structuredType.Properties().ToDictionary( p => p.Name, StringComparer.OrdinalIgnoreCase ); + var structuralProperties = new Dictionary( StringComparer.OrdinalIgnoreCase ); + var mappedClrProperties = new Dictionary(); var clrTypeMatchesEdmType = true; var hasUnfinishedTypes = false; var dependentProperties = new List(); + foreach ( var property in structuredType.Properties() ) + { + structuralProperties.Add( property.Name, property ); + var clrProperty = edmModel.GetAnnotationValue( property )?.ClrPropertyInfo; + if ( clrProperty != null ) + { + mappedClrProperties.Add( clrProperty, property ); + } + } + foreach ( var property in clrType.GetProperties( bindingFlags ) ) { - if ( !structuralProperties.TryGetValue( property.Name, out var structuralProperty ) ) + if ( !structuralProperties.TryGetValue( property.Name, out var structuralProperty ) && + !mappedClrProperties.TryGetValue( property, out structuralProperty ) ) { - var name = GetNameFromAttribute( property ); - if ( string.IsNullOrEmpty( name ) || !structuralProperties.TryGetValue( name, out structuralProperty ) ) - { - clrTypeMatchesEdmType = false; - continue; - } + clrTypeMatchesEdmType = false; + continue; } var structuredTypeRef = structuralProperty.Type; @@ -315,11 +323,6 @@ static ModuleBuilder CreateModuleForApiVersion( ApiVersion apiVersion ) return assemblyBuilder.DefineDynamicModule( "" ); } - static string GetNameFromAttribute( MemberInfo property ) - { - return property.GetCustomAttribute()?.Name; - } - sealed class BuilderContext { readonly Lazy moduleBuilder; diff --git a/test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/Contact.cs b/test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/Contact.cs index 6f27bdff..3b371314 100644 --- a/test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/Contact.cs +++ b/test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/Contact.cs @@ -1,26 +1,28 @@ -using System.Runtime.Serialization; - + namespace Microsoft.AspNet.OData { using System.Collections.Generic; + using System.Runtime.Serialization; + [DataContract] public class Contact { [DataMember] public int ContactId { get; set; } - [DataMember(Name = "first_name")] + + [DataMember( Name = "first_name" )] public string FirstName { get; set; } - [DataMember] - public string LastName { get; set; } [DataMember] + public string LastName { get; set; } - public string Email { get; set; } [DataMember] + public string Email { get; set; } - public string Phone { get; set; } [DataMember] + public string Phone { get; set; } + [DataMember] public List
Addresses { get; set; } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/DefaultModelTypeBuilderTest.cs b/test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/DefaultModelTypeBuilderTest.cs index 23ec5b44..464cce34 100644 --- a/test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/DefaultModelTypeBuilderTest.cs +++ b/test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/DefaultModelTypeBuilderTest.cs @@ -422,7 +422,7 @@ public void substituted_type_should_have_renamed_with_attribute_properties_from_ var substitutedType = originalType.SubstituteIfNecessary( context ); // assert - substitutedType.Should().HaveProperty(nameof( Contact.FirstName ) ); + substitutedType.Should().HaveProperty( nameof( Contact.FirstName ) ); } From 7f4b3d2f792ddc0aa69fae9412e6a35952755fca Mon Sep 17 00:00:00 2001 From: Sergei Vedishchev Date: Tue, 16 Jul 2019 13:03:43 +0200 Subject: [PATCH 6/9] unit test asserts for the DataMember attribute --- .../AspNet.OData/DefaultModelTypeBuilderTest.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/DefaultModelTypeBuilderTest.cs b/test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/DefaultModelTypeBuilderTest.cs index 464cce34..aef4a2ba 100644 --- a/test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/DefaultModelTypeBuilderTest.cs +++ b/test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/DefaultModelTypeBuilderTest.cs @@ -1,4 +1,7 @@ -namespace Microsoft.AspNet.OData +using System.Runtime.Serialization; +using FluentAssertions.Common; + +namespace Microsoft.AspNet.OData { using FluentAssertions; using Microsoft.AspNet.OData.Builder; @@ -423,6 +426,8 @@ public void substituted_type_should_have_renamed_with_attribute_properties_from_ // assert substitutedType.Should().HaveProperty( nameof( Contact.FirstName ) ); + substitutedType.GetRuntimeProperty( nameof( Contact.FirstName ) ).Should().NotBeNull(); + substitutedType.GetRuntimeProperty( nameof( Contact.FirstName ) ).HasAttribute(); } From 76384410c6db10a9800d997ae6bc374f695647eb Mon Sep 17 00:00:00 2001 From: Sergei Vedishchev Date: Tue, 16 Jul 2019 17:33:31 +0200 Subject: [PATCH 7/9] formatting --- .../AspNet.OData/DefaultModelTypeBuilderTest.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/DefaultModelTypeBuilderTest.cs b/test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/DefaultModelTypeBuilderTest.cs index aef4a2ba..09abc64f 100644 --- a/test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/DefaultModelTypeBuilderTest.cs +++ b/test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/DefaultModelTypeBuilderTest.cs @@ -1,9 +1,8 @@ -using System.Runtime.Serialization; -using FluentAssertions.Common; - -namespace Microsoft.AspNet.OData +namespace Microsoft.AspNet.OData { using FluentAssertions; + using FluentAssertions.Common; + using System.Runtime.Serialization; using Microsoft.AspNet.OData.Builder; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; @@ -430,7 +429,6 @@ public void substituted_type_should_have_renamed_with_attribute_properties_from_ substitutedType.GetRuntimeProperty( nameof( Contact.FirstName ) ).HasAttribute(); } - public static IEnumerable SubstitutionNotRequiredData { get From 08943ef5160be88af5738ecaa792e7d82ae0167c Mon Sep 17 00:00:00 2001 From: Sergei Vedishchev Date: Thu, 18 Jul 2019 00:44:04 +0200 Subject: [PATCH 8/9] dependencies with custom atttributes --- .../Configuration/ApiVersions.cs | 1 - .../Configuration/OrderModelConfiguration.cs | 2 - .../Configuration/PersonModelConfiguration.cs | 4 - .../Configuration/Ten99ModelConfiguration.cs | 78 ------------------- .../Models/AccountDetails.cs | 20 ----- .../SwaggerODataSample/Models/Address.cs | 23 ++---- .../SwaggerODataSample/Models/Ten99BModel.cs | 43 ---------- .../Models/Ten99DivModel.cs | 35 --------- .../SwaggerODataSample/Models/TrustModel.cs | 34 -------- .../SwaggerODataSample/V4/Ten99BController.cs | 41 ---------- .../V4/Ten99DivController.cs | 39 ---------- .../AspNet.OData/DefaultModelTypeBuilder.cs | 20 ++--- .../AspNet.OData/PropertyDependency.cs | 10 ++- .../AspNet.OData/Contact.cs | 7 +- .../DefaultModelTypeBuilderTest.cs | 38 ++++++++- .../AspNet.OData/Email.cs | 14 ++++ 16 files changed, 80 insertions(+), 329 deletions(-) delete mode 100644 samples/aspnetcore/SwaggerODataSample/Configuration/Ten99ModelConfiguration.cs delete mode 100644 samples/aspnetcore/SwaggerODataSample/Models/AccountDetails.cs delete mode 100644 samples/aspnetcore/SwaggerODataSample/Models/Ten99BModel.cs delete mode 100644 samples/aspnetcore/SwaggerODataSample/Models/Ten99DivModel.cs delete mode 100644 samples/aspnetcore/SwaggerODataSample/Models/TrustModel.cs delete mode 100644 samples/aspnetcore/SwaggerODataSample/V4/Ten99BController.cs delete mode 100644 samples/aspnetcore/SwaggerODataSample/V4/Ten99DivController.cs create mode 100644 test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/Email.cs diff --git a/samples/aspnetcore/SwaggerODataSample/Configuration/ApiVersions.cs b/samples/aspnetcore/SwaggerODataSample/Configuration/ApiVersions.cs index 23ac96e2..527596f1 100644 --- a/samples/aspnetcore/SwaggerODataSample/Configuration/ApiVersions.cs +++ b/samples/aspnetcore/SwaggerODataSample/Configuration/ApiVersions.cs @@ -7,6 +7,5 @@ static class ApiVersions internal static readonly ApiVersion V1 = new ApiVersion( 1, 0 ); internal static readonly ApiVersion V2 = new ApiVersion( 2, 0 ); internal static readonly ApiVersion V3 = new ApiVersion( 3, 0 ); - internal static readonly ApiVersion V4 = new ApiVersion( 4, 0 ); } } \ No newline at end of file diff --git a/samples/aspnetcore/SwaggerODataSample/Configuration/OrderModelConfiguration.cs b/samples/aspnetcore/SwaggerODataSample/Configuration/OrderModelConfiguration.cs index d206198b..652dd3ee 100644 --- a/samples/aspnetcore/SwaggerODataSample/Configuration/OrderModelConfiguration.cs +++ b/samples/aspnetcore/SwaggerODataSample/Configuration/OrderModelConfiguration.cs @@ -16,8 +16,6 @@ public class OrderModelConfiguration : IModelConfiguration /// The API version associated with the . public void Apply( ODataModelBuilder builder, ApiVersion apiVersion ) { - if ( apiVersion >= ApiVersions.V4 ) return; - var order = builder.EntitySet( "Orders" ).EntityType.HasKey( o => o.Id ); var lineItem = builder.EntityType().HasKey( li => li.Number ); diff --git a/samples/aspnetcore/SwaggerODataSample/Configuration/PersonModelConfiguration.cs b/samples/aspnetcore/SwaggerODataSample/Configuration/PersonModelConfiguration.cs index 2e418856..c2c55bfe 100644 --- a/samples/aspnetcore/SwaggerODataSample/Configuration/PersonModelConfiguration.cs +++ b/samples/aspnetcore/SwaggerODataSample/Configuration/PersonModelConfiguration.cs @@ -17,12 +17,8 @@ public class PersonModelConfiguration : IModelConfiguration /// The API version associated with the . public void Apply( ODataModelBuilder builder, ApiVersion apiVersion ) { - if ( apiVersion >= ApiVersions.V4 ) return; var person = builder.EntitySet( "People" ).EntityType; var address = builder.EntityType
().HasKey( a => a.Id ); - var subAddress = builder.EntityType().HasKey( a => a.Id ); - address.ContainsOptional( p => p.SubAddress ); - subAddress.ContainsOptional( p => p.InnerAddress ); person.HasKey( p => p.Id ); person.Select().OrderBy( "firstName", "lastName" ); diff --git a/samples/aspnetcore/SwaggerODataSample/Configuration/Ten99ModelConfiguration.cs b/samples/aspnetcore/SwaggerODataSample/Configuration/Ten99ModelConfiguration.cs deleted file mode 100644 index c7ce15d8..00000000 --- a/samples/aspnetcore/SwaggerODataSample/Configuration/Ten99ModelConfiguration.cs +++ /dev/null @@ -1,78 +0,0 @@ -using Microsoft.AspNet.OData.Builder; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Examples.Models; - -namespace Microsoft.Examples.Configuration -{ - public class Ten99ModelConfiguration : IModelConfiguration - { - /// - /// Applies model configurations using the provided builder for the specified API version. - /// - /// The builder used to apply configurations. - /// The API version associated with the . - public void Apply( ODataModelBuilder builder, ApiVersion apiVersion ) - { - if ( apiVersion < ApiVersions.V4 ) - { - return; - } - - var trust = builder.EntityType();//.HasKey( e => e.TrustKey ); - trust.HasKey( e => new { e.TrustKey, e.YrSeq, e.PermSeq } ); - //trust.Name = "Account"; - - var trustPerm = builder.EntityType();//.HasKey( e => e.TrustKey ); - trustPerm.HasKey( e => new { e.TrustKey, e.PermSeq } ); - //trustPerm.Name = "AccountDetails"; - - trust.ContainsOptional( t => t.Details );//.Name = "account_details"; - //trust.Ignore( t => t.TrustKey ); - //trust.Ignore( t => t.PermSeq ); - //trust.Property( t => t.YrSeq ).Name = "yr_seq"; - - - //trustPerm.Ignore( t => t.TrustKey ); - //trustPerm.Ignore( t => t.PermSeq ); - //trustPerm.Filter() - // .Count() - // .Expand() - // .OrderBy() - // .Page() - // .Select(); - - var ten99b = builder.EntitySet( "Ten99B" ).EntityType; - ten99b.HasKey( o => new { o.YrSeq, o.Pan } ); - //ten99b.HasKey( o => o.YrSeq ); - //ten99b.HasKey( o => o.Pan ); - - ten99b.ContainsOptional( t => t.Account ); - //ten99b.Name = "account"; - //ten99b.Ignore( t => t.TrustKey ); - //ten99b.Property( t => t.YrSeq ).Name = "yr_seq"; - //ten99b.Filter() - // .Count() - // .Expand() - // .OrderBy() - // .Page() - // .Select(); - - var ten99div = builder.EntitySet( "Ten99Div" ).EntityType; - ten99div.HasKey( o => new { o.YrSeq, o.Pan } ); - //ten99div.HasKey( o => o.YrSeq ); - //ten99div.HasKey( o => o.Pan ); - - - //ten99div.Ignore( t => t.TrustKey ); - //ten99div.Property( t => t.YrSeq ).Name = "yr_seq"; - ten99div.ContainsOptional( t => t.Account );//.Name = "account"; - //ten99div.Filter() - // .Count() - // .Expand() - // .OrderBy() - // .Page() - // .Select(); - - } - } -} \ No newline at end of file diff --git a/samples/aspnetcore/SwaggerODataSample/Models/AccountDetails.cs b/samples/aspnetcore/SwaggerODataSample/Models/AccountDetails.cs deleted file mode 100644 index 4f36d450..00000000 --- a/samples/aspnetcore/SwaggerODataSample/Models/AccountDetails.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Runtime.Serialization; -using Microsoft.AspNet.OData.Query; - -namespace Microsoft.Examples.Models -{ - //[Select] - [DataContract] - public class AccountDetails - { - [DataMember] - public int TrustKey { get; set; } - [DataMember] - public byte PermSeq { get; set; } - /// - /// Taxpayer name - /// - [DataMember( Name = "name_1" )] - public string Name1 { get; set; } - } -} \ No newline at end of file diff --git a/samples/aspnetcore/SwaggerODataSample/Models/Address.cs b/samples/aspnetcore/SwaggerODataSample/Models/Address.cs index 5459e26a..e6e21a96 100644 --- a/samples/aspnetcore/SwaggerODataSample/Models/Address.cs +++ b/samples/aspnetcore/SwaggerODataSample/Models/Address.cs @@ -1,54 +1,45 @@ namespace Microsoft.Examples.Models { - using Microsoft.AspNet.OData.Query; - using System; + using System.Runtime.Serialization; /// /// Represents an address. /// + [DataContract] public class Address { /// /// Gets or sets the address identifier. /// + [IgnoreDataMember] public int Id { get; set; } /// /// Gets or sets the street address. /// /// The street address. + [DataMember] public string Street { get; set; } /// /// Gets or sets the address city. /// /// The address city. + [DataMember] public string City { get; set; } /// /// Gets or sets the address state. /// /// The address state. + [DataMember] public string State { get; set; } /// /// Gets or sets the address zip code. /// /// The address zip code. + [DataMember(Name = "zip")] public string ZipCode { get; set; } - /// - /// Gets or sets the sub address. - /// - public SubAddress SubAddress { get; set; } - - } - - public class SubAddress - { - public int Id { get; set; } - /// - /// Person inner adderrss - /// - public Address InnerAddress { get; set; } } } \ No newline at end of file diff --git a/samples/aspnetcore/SwaggerODataSample/Models/Ten99BModel.cs b/samples/aspnetcore/SwaggerODataSample/Models/Ten99BModel.cs deleted file mode 100644 index b034d605..00000000 --- a/samples/aspnetcore/SwaggerODataSample/Models/Ten99BModel.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System.Runtime.Serialization; -using Microsoft.AspNet.OData.Builder; - -namespace Microsoft.Examples.Models -{ - [DataContract] - public class Ten99BModel - { - /// - /// Product Access Number - /// - [DataMember( Name = "pan" )] - public string Pan { get; set; } - - /// - /// Account number - /// - [DataMember( Name = "account_number" )] - //[DataMember] - public string TrustNo { get; set; } - /// - /// Key - /// - [DataMember( Name = "key_trust")] - public int TrustKey { get; set; } - - /// - /// Tax year and sequence - /// - [DataMember( Name = "yr_seq" )] - //[DataMember] - public short YrSeq { get; set; } - - /// - /// Account - /// - [DataMember( Name = "account_trust" )] - //[DataMember] - //[Contained] - public TrustModel Account { get; set; } - - } -} \ No newline at end of file diff --git a/samples/aspnetcore/SwaggerODataSample/Models/Ten99DivModel.cs b/samples/aspnetcore/SwaggerODataSample/Models/Ten99DivModel.cs deleted file mode 100644 index 3523db47..00000000 --- a/samples/aspnetcore/SwaggerODataSample/Models/Ten99DivModel.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Runtime.Serialization; - -namespace Microsoft.Examples.Models -{ - [DataContract] - public class Ten99DivModel - { - /// - /// Product Access Number - /// - [DataMember( Name = "pan" )] - public string Pan { get; set; } - /// - /// Account number - /// - [DataMember( Name = "account_number" )] - public string TrustNo { get; set; } - /// - /// Key - /// - [DataMember] - public int TrustKey { get; set; } - /// - /// Tax year and sequence - /// - [DataMember( Name = "yr_seq" )] - public short YrSeq { get; set; } - /// - /// Account - /// - [DataMember( Name = "account" )] - public TrustModel Account { get; set; } - - } -} \ No newline at end of file diff --git a/samples/aspnetcore/SwaggerODataSample/Models/TrustModel.cs b/samples/aspnetcore/SwaggerODataSample/Models/TrustModel.cs deleted file mode 100644 index a67b9855..00000000 --- a/samples/aspnetcore/SwaggerODataSample/Models/TrustModel.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Runtime.Serialization; -using Microsoft.AspNet.OData.Builder; -using Microsoft.AspNet.OData.Query; - -namespace Microsoft.Examples.Models -{ - //[Select] - [DataContract] - public class TrustModel - { - /// - /// Key - /// - [DataMember] - public int TrustKey { get; set; } - /// - /// Perm sequence - /// - [DataMember] - public byte PermSeq { get; set; } - /// - /// Tax year and sequence - /// - [DataMember( Name = "yr_seq" )] - public short YrSeq { get; set; } - /// - /// Account details - /// - [DataMember( Name = "account_details" )] - //[DataMember] - //[Contained] - public AccountDetails Details { get; set; } - } -} \ No newline at end of file diff --git a/samples/aspnetcore/SwaggerODataSample/V4/Ten99BController.cs b/samples/aspnetcore/SwaggerODataSample/V4/Ten99BController.cs deleted file mode 100644 index cb6bb812..00000000 --- a/samples/aspnetcore/SwaggerODataSample/V4/Ten99BController.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.AspNet.OData; -using Microsoft.AspNet.OData.Routing; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Examples.Models; -using static Microsoft.AspNet.OData.Query.AllowedQueryOptions; -using static Microsoft.AspNetCore.Http.StatusCodes; - -namespace Microsoft.Examples.V4 -{ - [ApiVersion( "4.0" )] - [ODataRoutePrefix( "Ten99B" )] - public class Ten99BController : ODataController - { - /// - /// Retrieves all orders. - /// - /// Tax year - /// PAN - /// All available orders. - /// Orders successfully retrieved. - /// The order is invalid. - [ODataRoute( "({year},{pan})" )] - [Produces( "application/json" )] - [ProducesResponseType( typeof( IQueryable ), Status200OK )] - [EnableQuery( MaxTop = 100, AllowedQueryOptions = Select | Top | Skip | Count )] - public IQueryable Get( [FromODataUri] short year, [FromODataUri] string pan ) - { - var orders = new[] - { - new Ten99BModel {Pan = pan, TrustNo = "1", YrSeq = year, Account = new TrustModel {YrSeq = year, Details = new AccountDetails { Name1 = pan }}}, - new Ten99BModel {Pan = pan, TrustNo = "2", YrSeq = year, Account = new TrustModel {YrSeq = year, Details = new AccountDetails { Name1 = pan }}}, - new Ten99BModel {Pan = pan, TrustNo = "3", YrSeq = year, Account = new TrustModel {YrSeq = year, Details = new AccountDetails { Name1 = pan }}} - }; - - return orders.AsQueryable(); - } - } -} \ No newline at end of file diff --git a/samples/aspnetcore/SwaggerODataSample/V4/Ten99DivController.cs b/samples/aspnetcore/SwaggerODataSample/V4/Ten99DivController.cs deleted file mode 100644 index b51cfe09..00000000 --- a/samples/aspnetcore/SwaggerODataSample/V4/Ten99DivController.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Microsoft.AspNet.OData; -using Microsoft.AspNet.OData.Query; -using Microsoft.AspNet.OData.Routing; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Examples.Models; - -namespace Microsoft.Examples.V4 -{ - [ApiVersion( "4.0" )] - [ODataRoutePrefix( "Ten99Div" )] - public class Ten99DivController : ODataController - { - /// - /// Retrieves all orders. - /// - /// All available orders. - /// Orders successfully retrieved. - /// The order is invalid. - [ODataRoute( "({year},{pan})" )] - [ProducesResponseType( typeof( IQueryable ), StatusCodes.Status200OK )] - [EnableQuery( MaxTop = 100, AllowedQueryOptions = AllowedQueryOptions.Select | AllowedQueryOptions.Top | AllowedQueryOptions.Skip | AllowedQueryOptions.Count )] - [Produces( "application/json" )] - public IQueryable Get( [FromODataUri] short year, [FromODataUri] string pan ) - { - var orders = new[] - { - new Ten99DivModel {Pan = pan, TrustNo = "1", YrSeq = year, Account = new TrustModel {YrSeq = year, Details = new AccountDetails {Name1 = pan}}}, - new Ten99DivModel {Pan = pan, TrustNo = "2", YrSeq = year, Account = new TrustModel {YrSeq = year, Details = new AccountDetails {Name1 = pan}}}, - new Ten99DivModel {Pan = pan, TrustNo = "3", YrSeq = year, Account = new TrustModel {YrSeq = year, Details = new AccountDetails {Name1 = pan}}} - }; - - return orders.AsQueryable(); - } - - } -} \ No newline at end of file diff --git a/src/Common.OData.ApiExplorer/AspNet.OData/DefaultModelTypeBuilder.cs b/src/Common.OData.ApiExplorer/AspNet.OData/DefaultModelTypeBuilder.cs index 2dddb4a3..0a66863b 100644 --- a/src/Common.OData.ApiExplorer/AspNet.OData/DefaultModelTypeBuilder.cs +++ b/src/Common.OData.ApiExplorer/AspNet.OData/DefaultModelTypeBuilder.cs @@ -141,7 +141,7 @@ static Type GenerateTypeIfNeeded( IEdmStructuredType structuredType, BuilderCont { clrTypeMatchesEdmType = false; hasUnfinishedTypes = true; - dependentProperties.Add( new PropertyDependency( elementKey, true, property.Name ) ); + dependentProperties.Add( new PropertyDependency( elementKey, true, property.Name, property.DeclaredAttributes() ) ); continue; } @@ -174,7 +174,7 @@ static Type GenerateTypeIfNeeded( IEdmStructuredType structuredType, BuilderCont { clrTypeMatchesEdmType = false; hasUnfinishedTypes = true; - dependentProperties.Add( new PropertyDependency( propertyTypeKey, false, property.Name ) ); + dependentProperties.Add( new PropertyDependency( propertyTypeKey, false, property.Name, property.DeclaredAttributes() ) ); continue; } } @@ -244,12 +244,7 @@ static TypeBuilder CreateTypeBuilderFromSignature( ModuleBuilder moduleBuilder, { var type = property.Type; var name = property.Name; - var propertyBuilder = AddProperty( typeBuilder, type, name ); - - foreach ( var attribute in property.Attributes ) - { - propertyBuilder.SetCustomAttribute( attribute ); - } + AddProperty( typeBuilder, type, name, property.Attributes ); } return typeBuilder; @@ -270,7 +265,7 @@ static IDictionary ResolveDependencies( BuilderContext con dependentOnType = IEnumerableOfT.MakeGenericType( dependentOnType ).GetTypeInfo(); } - AddProperty( propertyDependency.DependentType, dependentOnType, propertyDependency.PropertyName ); + AddProperty( propertyDependency.DependentType, dependentOnType, propertyDependency.PropertyName, propertyDependency.CustomAttributes ); } var keys = edmTypes.Keys.ToArray(); @@ -288,7 +283,7 @@ static IDictionary ResolveDependencies( BuilderContext con return edmTypes; } - static PropertyBuilder AddProperty( TypeBuilder addTo, Type shouldBeAdded, string name ) + static PropertyBuilder AddProperty( TypeBuilder addTo, Type shouldBeAdded, string name, IEnumerable customAttributes ) { const MethodAttributes propertyMethodAttributes = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig; var field = addTo.DefineField( "_" + name, shouldBeAdded, FieldAttributes.Private ); @@ -309,6 +304,11 @@ static PropertyBuilder AddProperty( TypeBuilder addTo, Type shouldBeAdded, strin propertyBuilder.SetGetMethod( getter ); propertyBuilder.SetSetMethod( setter ); + foreach ( var attribute in customAttributes ) + { + propertyBuilder.SetCustomAttribute( attribute ); + } + return propertyBuilder; } diff --git a/src/Common.OData.ApiExplorer/AspNet.OData/PropertyDependency.cs b/src/Common.OData.ApiExplorer/AspNet.OData/PropertyDependency.cs index 956c04b2..2fa7d893 100644 --- a/src/Common.OData.ApiExplorer/AspNet.OData/PropertyDependency.cs +++ b/src/Common.OData.ApiExplorer/AspNet.OData/PropertyDependency.cs @@ -1,5 +1,6 @@ namespace Microsoft.AspNet.OData { + using System.Collections.Generic; using System.Reflection.Emit; /// @@ -13,13 +14,15 @@ internal class PropertyDependency /// The key of the type the property has a dependency on. /// The name of the property. /// Whether the property is a collection or not. - internal PropertyDependency( EdmTypeKey dependentOnTypeKey, bool isCollection, string propertyName ) + /// A collection of custom attribute builders. + internal PropertyDependency( EdmTypeKey dependentOnTypeKey, bool isCollection, string propertyName, IEnumerable customAttributes ) { Arg.NotNull( dependentOnTypeKey, nameof( dependentOnTypeKey ) ); Arg.NotNull( propertyName, nameof( propertyName ) ); DependentOnTypeKey = dependentOnTypeKey; PropertyName = propertyName; + CustomAttributes = customAttributes; IsCollection = isCollection; } @@ -42,5 +45,10 @@ internal PropertyDependency( EdmTypeKey dependentOnTypeKey, bool isCollection, /// Gets a value indicating whether the property is a collection. /// internal bool IsCollection { get; } + + /// + /// Gets custom attribute builders of the property. + /// + internal IEnumerable CustomAttributes { get; } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/Contact.cs b/test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/Contact.cs index 3b371314..f62058dc 100644 --- a/test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/Contact.cs +++ b/test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/Contact.cs @@ -1,5 +1,4 @@ - -namespace Microsoft.AspNet.OData +namespace Microsoft.AspNet.OData { using System.Collections.Generic; using System.Runtime.Serialization; @@ -17,9 +16,9 @@ public class Contact public string LastName { get; set; } [DataMember] - public string Email { get; set; } + public Email Email { get; set; } - [DataMember] + [DataMember( Name = "telephone" )] public string Phone { get; set; } [DataMember] diff --git a/test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/DefaultModelTypeBuilderTest.cs b/test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/DefaultModelTypeBuilderTest.cs index 09abc64f..269325e5 100644 --- a/test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/DefaultModelTypeBuilderTest.cs +++ b/test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/DefaultModelTypeBuilderTest.cs @@ -209,7 +209,7 @@ public void type_should_match_edm_with_child_entity_substitution( Type originalT nextType.Should().HaveProperty( nameof( Contact.ContactId ) ); nextType.Should().HaveProperty( nameof( Contact.FirstName ) ); nextType.Should().HaveProperty( nameof( Contact.LastName ) ); - nextType.Should().HaveProperty( nameof( Contact.Email ) ); + nextType.Should().HaveProperty( nameof( Contact.Email ) ); nextType.Should().HaveProperty( nameof( Contact.Phone ) ); nextType = nextType.GetRuntimeProperty( nameof( Contact.Addresses ) ).PropertyType.GetGenericArguments()[0]; nextType.GetRuntimeProperties().Should().HaveCount( 5 ); @@ -429,6 +429,42 @@ public void substituted_type_should_have_renamed_with_attribute_properties_from_ substitutedType.GetRuntimeProperty( nameof( Contact.FirstName ) ).HasAttribute(); } + [Fact] + public void substituted_type_should_keep_custom_attributes_on_dependency_property() + { + // arrange + var modelBuilder = new ODataConventionModelBuilder(); + modelBuilder.EntitySet( "Contacts" ); + + var context = NewContext( modelBuilder.GetEdmModel() ); + var originalType = typeof( Contact ); + + // act + var substitutedType = originalType.SubstituteIfNecessary( context ); + + // assert + substitutedType.GetRuntimeProperty( nameof( Contact.Email ) ).Should().NotBeNull(); + substitutedType.GetRuntimeProperty( nameof( Contact.Email ) ).HasAttribute(); + } + + [Fact] + public void substituted_type_should_keep_custom_attributes_on_collection_dependency_property() + { + // arrange + var modelBuilder = new ODataConventionModelBuilder(); + modelBuilder.EntitySet( "Contacts" ); + + var context = NewContext( modelBuilder.GetEdmModel() ); + var originalType = typeof( Contact ); + + // act + var substitutedType = originalType.SubstituteIfNecessary( context ); + + // assert + substitutedType.GetRuntimeProperty( nameof( Contact.Addresses ) ).Should().NotBeNull(); + substitutedType.GetRuntimeProperty( nameof( Contact.Addresses ) ).HasAttribute(); + } + public static IEnumerable SubstitutionNotRequiredData { get diff --git a/test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/Email.cs b/test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/Email.cs new file mode 100644 index 00000000..64a9541b --- /dev/null +++ b/test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/Email.cs @@ -0,0 +1,14 @@ +namespace Microsoft.AspNet.OData +{ + using System.Runtime.Serialization; + + [DataContract] + public class Email + { + [DataMember] + public string Server { get; set; } + + [DataMember] + public string Username { get; set; } + } +} \ No newline at end of file From af3c343cce9aaf7b859dbc84f737f9dd22bebf6d Mon Sep 17 00:00:00 2001 From: Sergei Vedishchev Date: Thu, 18 Jul 2019 01:07:30 +0200 Subject: [PATCH 9/9] clean up --- .../SwaggerODataSample/SwaggerODataSample.csproj | 1 - samples/aspnetcore/SwaggerODataSample/web.config | 13 +++++-------- .../AspNet.OData/MethodCommentAttribute.cs | 14 -------------- 3 files changed, 5 insertions(+), 23 deletions(-) delete mode 100644 test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/MethodCommentAttribute.cs diff --git a/samples/aspnetcore/SwaggerODataSample/SwaggerODataSample.csproj b/samples/aspnetcore/SwaggerODataSample/SwaggerODataSample.csproj index abb88ca4..eed4cc1d 100644 --- a/samples/aspnetcore/SwaggerODataSample/SwaggerODataSample.csproj +++ b/samples/aspnetcore/SwaggerODataSample/SwaggerODataSample.csproj @@ -11,7 +11,6 @@ - diff --git a/samples/aspnetcore/SwaggerODataSample/web.config b/samples/aspnetcore/SwaggerODataSample/web.config index a2cf1fe2..dc0514fc 100644 --- a/samples/aspnetcore/SwaggerODataSample/web.config +++ b/samples/aspnetcore/SwaggerODataSample/web.config @@ -1,17 +1,14 @@  + + - + - - - - - - + - \ No newline at end of file + diff --git a/test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/MethodCommentAttribute.cs b/test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/MethodCommentAttribute.cs deleted file mode 100644 index 370e951e..00000000 --- a/test/Microsoft.AspNetCore.OData.Versioning.ApiExplorer.Tests/AspNet.OData/MethodCommentAttribute.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace Microsoft.AspNet.OData -{ - [AttributeUsage( AttributeTargets.Method )] - public class MethodCommentAttribute : Attribute - { - public MethodCommentAttribute(string comment) - { - Comment = comment; - } - public string Comment { get; } - } -} \ No newline at end of file