From 1c61a5fc9d0323b8fec38f8b00b0b134297cdf3c Mon Sep 17 00:00:00 2001 From: Sam Xu Date: Mon, 9 Oct 2023 13:47:27 -0700 Subject: [PATCH] Fixes #2810, make action/function link works using lower camel case --- .../Builder/LinkGenerationHelpers.cs | 24 +++++++-- .../ActionLinkGenerationConventionTest.cs | 51 +++++++++++++++++++ 2 files changed, 71 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.AspNet.OData.Shared/Builder/LinkGenerationHelpers.cs b/src/Microsoft.AspNet.OData.Shared/Builder/LinkGenerationHelpers.cs index f638ba297e..e088a94120 100644 --- a/src/Microsoft.AspNet.OData.Shared/Builder/LinkGenerationHelpers.cs +++ b/src/Microsoft.AspNet.OData.Shared/Builder/LinkGenerationHelpers.cs @@ -159,7 +159,7 @@ internal static Uri GenerateActionLink(this ResourceSetContext feedContext, stri isNested: false); Contract.Assert(elementType != null); - IEdmTypeReference typeReference = model.FindDeclaredType(elementType).ToEdmTypeReference(true); + IEdmTypeReference typeReference = model.FindBindingType(elementType).ToEdmTypeReference(true); IEdmTypeReference collection = new EdmCollectionTypeReference(new EdmCollectionType(typeReference)); IEdmOperation operation = model.FindDeclaredOperations(actionName).First(); @@ -278,7 +278,7 @@ internal static Uri GenerateFunctionLink(this ResourceSetContext feedContext, st isNested: false); Contract.Assert(elementType != null); - IEdmTypeReference typeReference = model.FindDeclaredType(elementType).ToEdmTypeReference(true); + IEdmTypeReference typeReference = model.FindBindingType(elementType).ToEdmTypeReference(true); IEdmTypeReference collection = new EdmCollectionTypeReference(new EdmCollectionType(typeReference)); IEdmOperation operation = model.FindDeclaredOperations(functionName).First(); return feedContext.GenerateFunctionLink(collection, operation, parameterNames); @@ -351,7 +351,7 @@ internal static Uri GenerateActionLink(this ResourceContext resourceContext, str } IEdmModel model = resourceContext.EdmModel; - IEdmTypeReference typeReference = model.FindDeclaredType(bindingParameterType).ToEdmTypeReference(true); + IEdmTypeReference typeReference = model.FindBindingType(bindingParameterType).ToEdmTypeReference(true); IEdmOperation operation = model.FindDeclaredOperations(actionName).First(); return resourceContext.GenerateActionLink(typeReference, operation); } @@ -422,7 +422,7 @@ internal static Uri GenerateFunctionLink(this ResourceContext resourceContext, s } IEdmModel model = resourceContext.EdmModel; - IEdmTypeReference typeReference = model.FindDeclaredType(bindingParameterType).ToEdmTypeReference(true); + IEdmTypeReference typeReference = model.FindBindingType(bindingParameterType).ToEdmTypeReference(true); IEdmOperation operation = model.FindDeclaredOperations(functionName).First(); return resourceContext.GenerateFunctionLink(typeReference, operation, parameterNames); } @@ -661,5 +661,21 @@ private static IList GenerateContainmentODataPathSegments(this return pathSegments; } + + private static IEdmSchemaType FindBindingType(this IEdmModel model, string bindingParameterType) + { + IEdmSchemaType type = model.FindDeclaredType(bindingParameterType); + if (type != null) + { + return type; + } + + // When the customer uses 'OnModelCreating' to change the type name case, + // the bindingParameterType could mis-match the type defined in the model. + // So, let's loose the logic to use 'ignore case' lookup. + return model.SchemaElements.OfType() + .Where(e => string.Equals(bindingParameterType, e.FullName(), StringComparison.OrdinalIgnoreCase)) + .SingleOrDefault(); + } } } diff --git a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/Conventions/ActionLinkGenerationConventionTest.cs b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/Conventions/ActionLinkGenerationConventionTest.cs index 2bca3b79b5..b73318bc14 100644 --- a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/Conventions/ActionLinkGenerationConventionTest.cs +++ b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/Conventions/ActionLinkGenerationConventionTest.cs @@ -74,6 +74,57 @@ public void Convention_GeneratesUri_ForActionBoundToEntity() Assert.Equal("http://localhost:123/odata/Customers(109)/Default.MyAction", link.AbsoluteUri); } + [Fact] + public void Convention_GeneratesUri_ForActionBoundToEntity_UsingCamelCase() + { + // Arrange + ODataConventionModelBuilder builder = ODataConventionModelBuilderFactory.Create(); + builder.OnModelCreating += ApplyLowerCamelCase; + builder.EntitySet("Customers"); + var action = builder.EntityType().Action("simpleAction"); + action.Parameter("param"); + IEdmModel model = builder.GetEdmModel(); + + // Act + var configuration = RoutingConfigurationFactory.Create(); + configuration.MapODataServiceRoute("odata", "odata", model); + + var request = RequestFactory.Create(HttpMethod.Get, "http://localhost:123", configuration, "odata"); + + IEdmEntitySet customers = model.EntityContainer.FindEntitySet("Customers"); + var edmType = model.SchemaElements.OfType().FirstOrDefault(e => e.Name == "Customer"); + Assert.Null(edmType); // guard, since it's lower camel case, it should be null. + edmType = model.SchemaElements.OfType().FirstOrDefault(e => e.Name == "customer"); + Assert.NotNull(edmType); + + var serializerContext = ODataSerializerContextFactory.Create(model, customers, request); + var resourceContext = new ResourceContext(serializerContext, edmType.AsReference(), new Customer { Id = 109 }); + + // Assert + var edmAction = model.SchemaElements.OfType().First(f => f.Name == "simpleAction"); + Assert.NotNull(edmAction); + + OperationLinkBuilder actionLinkBuilder = model.GetOperationLinkBuilder(edmAction); + Uri link = actionLinkBuilder.BuildLink(resourceContext); + + Assert.Equal("http://localhost:123/odata/Customers(109)/Default.simpleAction", link.AbsoluteUri); + } + + internal static void ApplyLowerCamelCase(ODataConventionModelBuilder builder) + { + LowerCamelCaser lowerCamelCaser = new LowerCamelCaser(); + + // handle structural types & their properties + foreach (StructuralTypeConfiguration type in builder.StructuralTypes) + { + type.Name = lowerCamelCaser.ToLowerCamelCase(type.Name); + foreach (PropertyConfiguration property in type.Properties) + { + property.Name = lowerCamelCaser.ToLowerCamelCase(property.Name); + } + } + } + [Fact] public void Apply_WorksFor_ActionBoundToCollectionOfEntity() {