From 25b8d813cc3d09b7c545eda1cfeec0bf36f3308a Mon Sep 17 00:00:00 2001 From: wchan Date: Tue, 11 Oct 2016 11:07:42 -0700 Subject: [PATCH 01/10] OData/WebApi fixed to 'allow enum properties to be keys' (https://github.com/OData/WebApi/issues/138). Modifications to ODataSwaggerUtilities to allow Enum type as well for a key definition and not just primitive types. --- .../Descriptions/ODataSwaggerUtilities.cs | 26 +++++++++++++++---- .../Descriptions/ParameterExtensions.cs | 3 ++- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/Swashbuckle.OData/Descriptions/ODataSwaggerUtilities.cs b/Swashbuckle.OData/Descriptions/ODataSwaggerUtilities.cs index a63c7a7..bcbaecb 100644 --- a/Swashbuckle.OData/Descriptions/ODataSwaggerUtilities.cs +++ b/Swashbuckle.OData/Descriptions/ODataSwaggerUtilities.cs @@ -164,11 +164,27 @@ public static PathItem CreateSwaggerPathForEntity(IEdmEntitySet entitySet, OData foreach (var key in entitySet.GetEntityType().GetKey()) { Contract.Assume(key != null); - string format; - var keyDefinition = key.GetPropertyType().GetDefinition() as IEdmPrimitiveType; - Contract.Assume(keyDefinition != null); - var type = GetPrimitiveTypeAndFormat(keyDefinition, out format); - keyParameters.Parameter(key.Name, "path", "key: " + key.Name, type, true, format); + + // Create key parameters for primitive and enum types + IEdmType keyDefinitionAsType = null; + var keyDefinition = key.GetPropertyType().GetDefinition(); + + if (EdmTypeKind.Primitive == keyDefinition.TypeKind) + { + keyDefinitionAsType = keyDefinition as IEdmPrimitiveType; + } + else if(EdmTypeKind.Enum == keyDefinition.TypeKind) + { + keyDefinitionAsType = keyDefinition as IEdmEnumType; + } + Contract.Assume(keyDefinitionAsType != null); + + keyParameters.Parameter( + key.Name, + "path", + "key: " + key.Name, + keyDefinitionAsType, + true); } return new PathItem diff --git a/Swashbuckle.OData/Descriptions/ParameterExtensions.cs b/Swashbuckle.OData/Descriptions/ParameterExtensions.cs index 65ce819..221ea51 100644 --- a/Swashbuckle.OData/Descriptions/ParameterExtensions.cs +++ b/Swashbuckle.OData/Descriptions/ParameterExtensions.cs @@ -109,7 +109,8 @@ public static string GenerateSamplePathParameterValue(this Parameter parameter) case "string": if (parameter.@enum != null && parameter.@enum.Any()) { - return parameter.@enum.First().ToString(); + return String.Format("\'{0}\'", + parameter.@enum.First().ToString()); } return "\'SampleString\'"; case "boolean": From e0a13ef6cd4f12c814353882ed06f3aac21edd31 Mon Sep 17 00:00:00 2001 From: wchan Date: Wed, 12 Oct 2016 11:27:13 -0700 Subject: [PATCH 02/10] Enum and Enum as composite key model/controller odata routes added in Sample for testing use. Web.config settings added to prevent loading of routes for finer grain debugging. Library versions update in coverage.bat. --- .../App_Start/ODataConfig.cs | 103 ++++++++++++++---- .../App_Start/WebApiConfig.cs | 13 ++- .../Models/ProductWithEnumKey.cs | 27 +++++ ...oductWithCompositeEnumIntKeysController.cs | 64 +++++++++++ .../ProductWithEnumKeysController.cs | 59 ++++++++++ .../Swashbuckle.OData.Sample.csproj | 3 + Swashbuckle.OData.Sample/Web.config | 10 ++ .../Descriptions/OperationExtensions.cs | 6 +- coverage.bat | 8 +- 9 files changed, 265 insertions(+), 28 deletions(-) create mode 100644 Swashbuckle.OData.Sample/Models/ProductWithEnumKey.cs create mode 100644 Swashbuckle.OData.Sample/ODataControllers/ProductWithCompositeEnumIntKeysController.cs create mode 100644 Swashbuckle.OData.Sample/ODataControllers/ProductWithEnumKeysController.cs diff --git a/Swashbuckle.OData.Sample/App_Start/ODataConfig.cs b/Swashbuckle.OData.Sample/App_Start/ODataConfig.cs index 95b80f4..4f7dcf4 100644 --- a/Swashbuckle.OData.Sample/App_Start/ODataConfig.cs +++ b/Swashbuckle.OData.Sample/App_Start/ODataConfig.cs @@ -13,6 +13,7 @@ using SwashbuckleODataSample.Models; using SwashbuckleODataSample.Repositories; using SwashbuckleODataSample.Versioning; +using System.Web.Configuration; namespace SwashbuckleODataSample { @@ -28,7 +29,11 @@ public static void Register(HttpConfiguration config) private static async void ConfigureRestierOData(HttpConfiguration config) { - await config.MapRestierRoute>("RESTierRoute", "restier", new RestierBatchHandler(GlobalConfiguration.DefaultServer)); + if (true == System.Convert.ToBoolean( + WebConfigurationManager.AppSettings["LoadRestierRoutes"])) + { + await config.MapRestierRoute>("RESTierRoute", "restier", new RestierBatchHandler(GlobalConfiguration.DefaultServer)); + } } private static void ConfigureWebApiOData(HttpConfiguration config) @@ -36,28 +41,57 @@ private static void ConfigureWebApiOData(HttpConfiguration config) var controllerSelector = new ODataVersionControllerSelector(config); config.Services.Replace(typeof(IHttpControllerSelector), controllerSelector); - // Define a versioned route - config.MapODataServiceRoute("V1RouteVersioning", "odata/v1", GetVersionedModel()); - controllerSelector.RouteVersionSuffixMapping.Add("V1RouteVersioning", "V1"); - - // Define a versioned route that doesn't map to any controller - config.MapODataServiceRoute("odata/v2", "odata/v2", GetFakeModel()); - controllerSelector.RouteVersionSuffixMapping.Add("odata/v2", "V2"); - - // Define a custom route with custom routing conventions - var conventions = ODataRoutingConventions.CreateDefault(); - conventions.Insert(0, new CustomNavigationPropertyRoutingConvention()); - var customODataRoute = config.MapODataServiceRoute("CustomODataRoute", ODataRoutePrefix, GetCustomRouteModel(), batchHandler: null, pathHandler: new DefaultODataPathHandler(), routingConventions: conventions); - config.AddCustomSwaggerRoute(customODataRoute, "/Customers({Id})/Orders") - .Operation(HttpMethod.Post) - .PathParameter("Id") - .BodyParameter("order"); - - // Define a route to a controller class that contains functions - config.MapODataServiceRoute("FunctionsODataRoute", ODataRoutePrefix, GetFunctionsEdmModel()); - - // Define a default non- versioned route(default route should be at the end as a last catch-all) - config.MapODataServiceRoute("DefaultODataRoute", ODataRoutePrefix, GetDefaultModel()); + if (true == System.Convert.ToBoolean( + WebConfigurationManager.AppSettings["LoadVersionedRoutes"])) + { + // Define a versioned route + config.MapODataServiceRoute("V1RouteVersioning", "odata/v1", GetVersionedModel()); + controllerSelector.RouteVersionSuffixMapping.Add("V1RouteVersioning", "V1"); + + // Define a versioned route that doesn't map to any controller + config.MapODataServiceRoute("odata/v2", "odata/v2", GetFakeModel()); + controllerSelector.RouteVersionSuffixMapping.Add("odata/v2", "V2"); + } + + if (true == System.Convert.ToBoolean( + WebConfigurationManager.AppSettings["LoadCustomRoutes"])) + { + // Define a custom route with custom routing conventions + var conventions = ODataRoutingConventions.CreateDefault(); + conventions.Insert(0, new CustomNavigationPropertyRoutingConvention()); + var customODataRoute = config.MapODataServiceRoute("CustomODataRoute", ODataRoutePrefix, GetCustomRouteModel(), batchHandler: null, pathHandler: new DefaultODataPathHandler(), routingConventions: conventions); + config.AddCustomSwaggerRoute(customODataRoute, "/Customers({Id})/Orders") + .Operation(HttpMethod.Post) + .PathParameter("Id") + .BodyParameter("order"); + } + + if (true == System.Convert.ToBoolean( + WebConfigurationManager.AppSettings["LoadRoutesWithFunctions"])) + { + // Define a route to a controller class that contains functions + config.MapODataServiceRoute("FunctionsODataRoute", ODataRoutePrefix, GetFunctionsEdmModel()); + } + + if (true == System.Convert.ToBoolean( + WebConfigurationManager.AppSettings["LoadDefaultRoutes"])) + { + // Define a default non- versioned route(default route should be at the end as a last catch-all) + config.MapODataServiceRoute("DefaultODataRoute", ODataRoutePrefix, GetDefaultModel()); + } + + if (true == System.Convert.ToBoolean( + WebConfigurationManager.AppSettings["LoadEnumRoutes"])) + { + // Define a route with an enum as a key + config.MapODataServiceRoute("EnumODataRoute", + ODataRoutePrefix, + GetProductWithEnumKeyModel()); + // Define a route with an enum/int composite key + config.MapODataServiceRoute("EnumIntCompositeODataRoute", + ODataRoutePrefix, + GetProductWithCompositeEnumIntKeyModel()); + } } private static IEdmModel GetDefaultModel() @@ -174,5 +208,28 @@ private static IEdmModel GetFunctionsEdmModel() return builder.GetEdmModel(); } + + #region Enum Routes + private static IEdmModel GetProductWithEnumKeyModel() + { + var builder = new ODataConventionModelBuilder(); + builder.EnableLowerCamelCase(); + + builder.EntitySet("ProductWithEnumKeys"); + + return builder.GetEdmModel(); + } + + private static IEdmModel GetProductWithCompositeEnumIntKeyModel() + { + var builder = new ODataConventionModelBuilder(); + builder.EnableLowerCamelCase(); + + builder.EntitySet + ("ProductWithCompositeEnumIntKeys"); + + return builder.GetEdmModel(); + } + #endregion } } \ No newline at end of file diff --git a/Swashbuckle.OData.Sample/App_Start/WebApiConfig.cs b/Swashbuckle.OData.Sample/App_Start/WebApiConfig.cs index c59dca3..35954b3 100644 --- a/Swashbuckle.OData.Sample/App_Start/WebApiConfig.cs +++ b/Swashbuckle.OData.Sample/App_Start/WebApiConfig.cs @@ -1,4 +1,6 @@ -using System.Web.Http; +using System.Web.Configuration; +using System.Web.Http; +using System.Web.OData.Extensions; namespace SwashbuckleODataSample { @@ -6,9 +8,16 @@ public static class WebApiConfig { public static void Register(HttpConfiguration config) { + bool isPrefixFreeEnabled = System.Convert.ToBoolean( + WebConfigurationManager.AppSettings["EnableEnumPrefixFree"]); + config.EnableEnumPrefixFree(isPrefixFreeEnabled); config.MapHttpAttributeRoutes(); - config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}", new { id = RouteParameter.Optional }); + if (true == System.Convert.ToBoolean( + WebConfigurationManager.AppSettings["LoadDefaultApiRoutes"])) + { + config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}", new { id = RouteParameter.Optional }); + } } } } \ No newline at end of file diff --git a/Swashbuckle.OData.Sample/Models/ProductWithEnumKey.cs b/Swashbuckle.OData.Sample/Models/ProductWithEnumKey.cs new file mode 100644 index 0000000..a37083a --- /dev/null +++ b/Swashbuckle.OData.Sample/Models/ProductWithEnumKey.cs @@ -0,0 +1,27 @@ +using System.ComponentModel.DataAnnotations; + +namespace SwashbuckleODataSample.Models +{ + public class ProductWithEnumKey + { + [Key] + public MyEnum EnumValue { get; set; } + + public string Name { get; set; } + + public double Price { get; set; } + } + + public class ProductWithCompositeEnumIntKey + { + [Key] + public MyEnum EnumValue { get; set; } + + [Key] + public int Id { get; set; } + + public string Name { get; set; } + + public double Price { get; set; } + } +} \ No newline at end of file diff --git a/Swashbuckle.OData.Sample/ODataControllers/ProductWithCompositeEnumIntKeysController.cs b/Swashbuckle.OData.Sample/ODataControllers/ProductWithCompositeEnumIntKeysController.cs new file mode 100644 index 0000000..1b9aa11 --- /dev/null +++ b/Swashbuckle.OData.Sample/ODataControllers/ProductWithCompositeEnumIntKeysController.cs @@ -0,0 +1,64 @@ +using System.Collections.Generic; +using System.Linq; +using System.Web.Http; +using System.Web.OData; +using SwashbuckleODataSample.Models; +using System; + +namespace SwashbuckleODataSample.ODataControllers +{ + public class ProductWithCompositeEnumIntKeysController : ODataController + { + private static readonly Dictionary, + ProductWithCompositeEnumIntKey> DataCompositeKey; + + static ProductWithCompositeEnumIntKeysController() + { + DataCompositeKey = new Dictionary, + ProductWithCompositeEnumIntKey>() + { + { + Tuple.Create(MyEnum.ValueOne, 1), + new ProductWithCompositeEnumIntKey + { + EnumValue = MyEnum.ValueOne, + Id = 1, + Name = "ValueOneName", + Price = 101 + } + }, + { + Tuple.Create(MyEnum.ValueTwo, 2), + new ProductWithCompositeEnumIntKey + { + EnumValue = MyEnum.ValueTwo, + Id = 2, + Name = "ValueTwoName", + Price = 102 + } + } + }; + } + + /// + /// Query products + /// + [EnableQuery] + public IQueryable Get() + { + return DataCompositeKey.Values.AsQueryable(); + } + + /// + /// Query products by keys + /// + /// key enum value + /// key id + /// composite enum-int key model + [EnableQuery] + public IHttpActionResult Get([FromODataUri]MyEnum keyenumValue, [FromODataUri]int keyid) + { + return Ok(DataCompositeKey[Tuple.Create(keyenumValue, keyid)]); + } + } +} \ No newline at end of file diff --git a/Swashbuckle.OData.Sample/ODataControllers/ProductWithEnumKeysController.cs b/Swashbuckle.OData.Sample/ODataControllers/ProductWithEnumKeysController.cs new file mode 100644 index 0000000..892ec5b --- /dev/null +++ b/Swashbuckle.OData.Sample/ODataControllers/ProductWithEnumKeysController.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; +using System.Linq; +using System.Web.Http; +using System.Web.OData; +using SwashbuckleODataSample.Models; + +namespace SwashbuckleODataSample.ODataControllers +{ + public class ProductWithEnumKeysController : ODataController + { + private static readonly Dictionary DataEnumAsKey; + + static ProductWithEnumKeysController() + { + DataEnumAsKey = new Dictionary() + { + { + MyEnum.ValueOne, + new ProductWithEnumKey { + EnumValue = MyEnum.ValueOne, + Name = "ValueOneName", + Price = 101 + } + }, + { + MyEnum.ValueTwo, + new ProductWithEnumKey + { + EnumValue = MyEnum.ValueTwo, + Name = "ValueTwoName", + Price = 102 + } + } + }; + } + + /// + /// Query products + /// + [HttpGet] + [EnableQuery] + public IQueryable Get() + { + return DataEnumAsKey.Values.AsQueryable(); + } + + /// + /// Query product by enum key + /// + /// key enum value + /// project enum model + [HttpGet] + public IHttpActionResult Get([FromODataUri]MyEnum Key) + { + return Ok(DataEnumAsKey[Key]); + } + } +} \ No newline at end of file diff --git a/Swashbuckle.OData.Sample/Swashbuckle.OData.Sample.csproj b/Swashbuckle.OData.Sample/Swashbuckle.OData.Sample.csproj index 4e854d0..74c0cb2 100644 --- a/Swashbuckle.OData.Sample/Swashbuckle.OData.Sample.csproj +++ b/Swashbuckle.OData.Sample/Swashbuckle.OData.Sample.csproj @@ -146,6 +146,9 @@ + + + diff --git a/Swashbuckle.OData.Sample/Web.config b/Swashbuckle.OData.Sample/Web.config index f9f429e..21e5cbf 100644 --- a/Swashbuckle.OData.Sample/Web.config +++ b/Swashbuckle.OData.Sample/Web.config @@ -64,4 +64,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/Swashbuckle.OData/Descriptions/OperationExtensions.cs b/Swashbuckle.OData/Descriptions/OperationExtensions.cs index 7bfd583..beb6748 100644 --- a/Swashbuckle.OData/Descriptions/OperationExtensions.cs +++ b/Swashbuckle.OData/Descriptions/OperationExtensions.cs @@ -28,7 +28,11 @@ public static string GenerateSampleODataUri(this Operation operation, string ser { var prefix = new Uri(serviceRoot); - return new UriTemplate(pathTemplate).BindByName(prefix, parameters).ToString(); + var uriTemplate = new UriTemplate(pathTemplate) + .BindByName(prefix, parameters) + .ToString(); + + return uriTemplate.Replace("''","'"); } return serviceRoot.AppendPathSegment(pathTemplate); } diff --git a/coverage.bat b/coverage.bat index 65f03a8..11087df 100644 --- a/coverage.bat +++ b/coverage.bat @@ -1,5 +1,9 @@ -.\packages\OpenCover.4.6.166\tools\OpenCover.Console.exe -register:user "-filter:+[Swashbuckle.OData]* -[Swashbuckle.OData]System.*" "-target:.\packages\NUnit.Runners.2.6.4\tools\nunit-console-x86.exe" "-targetargs:/noshadow .\Swashbuckle.OData.Tests\bin\Debug\Swashbuckle.OData.Tests.dll" +set OpenCoverVersion=4.6.519 +set ReportGeneratorVersion=2.4.3.0 +set NUnitRunnersVersion=2.6.4 -.\packages\ReportGenerator.2.3.5.0\tools\ReportGenerator.exe "-reports:results.xml" "-targetdir:.\coverage" +.\packages\OpenCover.%OpenCoverVersion%\tools\OpenCover.Console.exe -register:user "-filter:+[Swashbuckle.OData]* -[Swashbuckle.OData]System.*" "-target:.\packages\NUnit.Runners.%NUnitRunnersVersion%\tools\nunit-console-x86.exe" "-targetargs:/noshadow .\Swashbuckle.OData.Tests\bin\Debug\Swashbuckle.OData.Tests.dll" + +.\packages\ReportGenerator.%ReportGeneratorVersion%\tools\ReportGenerator.exe "-reports:results.xml" "-targetdir:.\coverage" pause \ No newline at end of file From b51bbd73350989b51e1eaf35091f8f817dea6eea Mon Sep 17 00:00:00 2001 From: wchan Date: Thu, 13 Oct 2016 15:08:41 -0700 Subject: [PATCH 03/10] Modified ProductWithCompositeEnumIntKeysController to use simpler List collection type. --- .../ProductWithCompositeEnumIntKeysController.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/Swashbuckle.OData.Sample/ODataControllers/ProductWithCompositeEnumIntKeysController.cs b/Swashbuckle.OData.Sample/ODataControllers/ProductWithCompositeEnumIntKeysController.cs index 1b9aa11..073ffdd 100644 --- a/Swashbuckle.OData.Sample/ODataControllers/ProductWithCompositeEnumIntKeysController.cs +++ b/Swashbuckle.OData.Sample/ODataControllers/ProductWithCompositeEnumIntKeysController.cs @@ -9,16 +9,13 @@ namespace SwashbuckleODataSample.ODataControllers { public class ProductWithCompositeEnumIntKeysController : ODataController { - private static readonly Dictionary, - ProductWithCompositeEnumIntKey> DataCompositeKey; + private static readonly List DataCompositeKey; static ProductWithCompositeEnumIntKeysController() { - DataCompositeKey = new Dictionary, - ProductWithCompositeEnumIntKey>() + DataCompositeKey = new List() { { - Tuple.Create(MyEnum.ValueOne, 1), new ProductWithCompositeEnumIntKey { EnumValue = MyEnum.ValueOne, @@ -28,7 +25,6 @@ static ProductWithCompositeEnumIntKeysController() } }, { - Tuple.Create(MyEnum.ValueTwo, 2), new ProductWithCompositeEnumIntKey { EnumValue = MyEnum.ValueTwo, @@ -46,7 +42,7 @@ static ProductWithCompositeEnumIntKeysController() [EnableQuery] public IQueryable Get() { - return DataCompositeKey.Values.AsQueryable(); + return DataCompositeKey.AsQueryable(); } /// @@ -58,7 +54,9 @@ public IQueryable Get() [EnableQuery] public IHttpActionResult Get([FromODataUri]MyEnum keyenumValue, [FromODataUri]int keyid) { - return Ok(DataCompositeKey[Tuple.Create(keyenumValue, keyid)]); + return Ok(DataCompositeKey + .Where(x => x.EnumValue == keyenumValue + && x.Id == keyid)); } } } \ No newline at end of file From 7999275364ed454b7437b84bda4725ee94e5503f Mon Sep 17 00:00:00 2001 From: wchan Date: Thu, 13 Oct 2016 15:09:28 -0700 Subject: [PATCH 04/10] Reverted minor changes from OperationExtensions and ParameterExtensions. --- Swashbuckle.OData/Descriptions/OperationExtensions.cs | 6 +----- Swashbuckle.OData/Descriptions/ParameterExtensions.cs | 3 +-- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/Swashbuckle.OData/Descriptions/OperationExtensions.cs b/Swashbuckle.OData/Descriptions/OperationExtensions.cs index beb6748..7bfd583 100644 --- a/Swashbuckle.OData/Descriptions/OperationExtensions.cs +++ b/Swashbuckle.OData/Descriptions/OperationExtensions.cs @@ -28,11 +28,7 @@ public static string GenerateSampleODataUri(this Operation operation, string ser { var prefix = new Uri(serviceRoot); - var uriTemplate = new UriTemplate(pathTemplate) - .BindByName(prefix, parameters) - .ToString(); - - return uriTemplate.Replace("''","'"); + return new UriTemplate(pathTemplate).BindByName(prefix, parameters).ToString(); } return serviceRoot.AppendPathSegment(pathTemplate); } diff --git a/Swashbuckle.OData/Descriptions/ParameterExtensions.cs b/Swashbuckle.OData/Descriptions/ParameterExtensions.cs index 221ea51..65ce819 100644 --- a/Swashbuckle.OData/Descriptions/ParameterExtensions.cs +++ b/Swashbuckle.OData/Descriptions/ParameterExtensions.cs @@ -109,8 +109,7 @@ public static string GenerateSamplePathParameterValue(this Parameter parameter) case "string": if (parameter.@enum != null && parameter.@enum.Any()) { - return String.Format("\'{0}\'", - parameter.@enum.First().ToString()); + return parameter.@enum.First().ToString(); } return "\'SampleString\'"; case "boolean": From 3b4d1d39cfa50827b1bb0081693a406445cff9a1 Mon Sep 17 00:00:00 2001 From: wchan Date: Thu, 13 Oct 2016 15:38:07 -0700 Subject: [PATCH 05/10] Updates to path building and mapping methods to allow enum as key and to support the HttpConfiguration EnumPrefixFree setting. --- .../ApplyResourceDocumentation.cs | 4 +- .../EntityDataModelRouteGenerator.cs | 1 + .../Descriptions/MapByDescription.cs | 26 ++++- .../Descriptions/ODataSwaggerUtilities.cs | 94 +++++++++++++++++-- 4 files changed, 116 insertions(+), 9 deletions(-) diff --git a/Swashbuckle.OData.Sample/DocumentFilters/ApplyResourceDocumentation.cs b/Swashbuckle.OData.Sample/DocumentFilters/ApplyResourceDocumentation.cs index ae5fdf7..4695d30 100644 --- a/Swashbuckle.OData.Sample/DocumentFilters/ApplyResourceDocumentation.cs +++ b/Swashbuckle.OData.Sample/DocumentFilters/ApplyResourceDocumentation.cs @@ -18,7 +18,9 @@ public void Apply(SwaggerDocument swaggerDoc, SchemaRegistry schemaRegistry, IAp new Tag { name = "Orders", description = "an ODataController resource" }, new Tag { name = "CustomersV1", description = "a versioned ODataController resource" }, new Tag { name = "Users", description = "a RESTier resource" }, - new Tag { name = "Products", description = "demonstrates OData functions and actions" } + new Tag { name = "Products", description = "demonstrates OData functions and actions" }, + new Tag { name = "ProductWithCompositeEnumIntKeys", description = "demonstrates composite keys with an enum as a key" }, + new Tag { name = "ProductWithEnumKeys", description = "demonstrates use of enum as a key" }, }; } } diff --git a/Swashbuckle.OData/Descriptions/EntityDataModelRouteGenerator.cs b/Swashbuckle.OData/Descriptions/EntityDataModelRouteGenerator.cs index dc81c44..5f1c748 100644 --- a/Swashbuckle.OData/Descriptions/EntityDataModelRouteGenerator.cs +++ b/Swashbuckle.OData/Descriptions/EntityDataModelRouteGenerator.cs @@ -19,6 +19,7 @@ internal class EntityDataModelRouteGenerator : ISwaggerRouteGenerator { public IEnumerable Generate(HttpConfiguration httpConfig) { + ODataSwaggerUtilities.SetHttpConfig(httpConfig); return httpConfig.GetODataRoutes().SelectMany(Generate); } diff --git a/Swashbuckle.OData/Descriptions/MapByDescription.cs b/Swashbuckle.OData/Descriptions/MapByDescription.cs index 2fd4f45..1cac548 100644 --- a/Swashbuckle.OData/Descriptions/MapByDescription.cs +++ b/Swashbuckle.OData/Descriptions/MapByDescription.cs @@ -2,17 +2,41 @@ using System.Linq; using System.Web.Http.Controllers; using Swashbuckle.Swagger; +using System; namespace Swashbuckle.OData.Descriptions { internal class MapByDescription : IParameterMapper { + /// + /// The name for parameter keys in the route. + /// + private const string KeyName = "key"; + + /// + /// Swagger description substring dividing the key from the paramter name. + /// + private const string FindKeyReplacementSubStr = ": "; + public HttpParameterDescriptor Map(Parameter swaggerParameter, int parameterIndex, HttpActionDescriptor actionDescriptor) { // Maybe the parameter is a key parameter, e.g., where Id in the URI path maps to a parameter named 'key' if (swaggerParameter.description != null && swaggerParameter.description.StartsWith("key:")) { - var parameterDescriptor = actionDescriptor.GetParameters()?.SingleOrDefault(descriptor => descriptor.ParameterName == "key"); + // Find either a single 'key' in the route or composite keys + // which take the form of key + var keyParameterName = swaggerParameter + .description + .Replace(FindKeyReplacementSubStr, + String.Empty) + .ToLower(); + var parameterDescriptor = + actionDescriptor + .GetParameters()? + .SingleOrDefault(descriptor => + descriptor.ParameterName.ToLower() == KeyName + || descriptor.ParameterName.ToLower().Equals(keyParameterName) + ); if (parameterDescriptor != null && !parameterDescriptor.IsODataLibraryType()) { var httpControllerDescriptor = actionDescriptor.ControllerDescriptor; diff --git a/Swashbuckle.OData/Descriptions/ODataSwaggerUtilities.cs b/Swashbuckle.OData/Descriptions/ODataSwaggerUtilities.cs index bcbaecb..641200d 100644 --- a/Swashbuckle.OData/Descriptions/ODataSwaggerUtilities.cs +++ b/Swashbuckle.OData/Descriptions/ODataSwaggerUtilities.cs @@ -13,6 +13,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using Swashbuckle.Swagger; +using System.Web.Http; namespace Swashbuckle.OData.Descriptions { @@ -21,6 +22,38 @@ namespace Swashbuckle.OData.Descriptions /// internal static class ODataSwaggerUtilities { + /// + /// HttpConfiguration for ODataSwaggerUtilities + /// + public static HttpConfiguration HttpConfig = null; + + /// + /// Resolver Settings Key namespace + /// + private const string ResolverSettingsKey + = "System.Web.OData.ResolverSettingsKey"; + + /// + /// Enum Prefix Free property name + /// + private const string EnumPrefixFree = "EnumPrefixFree"; + + /// + /// The ending Path offset + /// + private const int EntityPathSubStrOffset = 1; + + /// + /// Sets the HttpConfig static variable for use in the + /// ODataSwaggerUtilities class. + /// + /// + public static void SetHttpConfig(HttpConfiguration httpConfig) + { + Contract.Requires(httpConfig != null); + HttpConfig = httpConfig; + } + /// /// Create the Swagger path for the Edm entity set. /// @@ -485,8 +518,8 @@ public static string GetPathForEntity(IEdmEntitySet entitySet) singleEntityPath = entitySet.GetEntityType().GetKey().Count() == 1 ? AppendSingleColumnKeyTemplate(entitySet, singleEntityPath) : AppendMultiColumnKeyTemplate(entitySet, singleEntityPath); - Contract.Assume(singleEntityPath.Length - 2 >= 0); - singleEntityPath = singleEntityPath.Substring(0, singleEntityPath.Length - 2); + Contract.Assume(singleEntityPath.Length - EntityPathSubStrOffset >= 0); + singleEntityPath = singleEntityPath.Substring(0, singleEntityPath.Length - EntityPathSubStrOffset); singleEntityPath += ")"; return singleEntityPath; @@ -498,7 +531,7 @@ private static string AppendSingleColumnKeyTemplate(IEdmEntitySet entitySet, str Contract.Ensures(Contract.Result() != null); var key = entitySet.GetEntityType().GetKey().Single(); - singleEntityPath += "{" + key.Name + "}, "; + singleEntityPath += GetParameterPathAssignment(key.Name, key.Type, false); return singleEntityPath; } @@ -509,7 +542,7 @@ private static string AppendMultiColumnKeyTemplate(IEdmEntitySet entitySet, stri foreach (var key in entitySet.GetEntityType().GetKey()) { Contract.Assume(key != null); - singleEntityPath += key.Name + "={" + key.Name + "}, "; + singleEntityPath += GetParameterPathAssignment(key.Name, key.Type, true); } Contract.Assume(singleEntityPath != null); return singleEntityPath; @@ -586,12 +619,39 @@ private static string GetFunctionParameterAssignmentPath(IEdmOperationParameter { Contract.Requires(parameter != null); - switch (parameter.Type.Definition.TypeKind) + return GetParameterPathAssignment(parameter.Name, parameter.Type, true); + } + + /// + /// Path builder for endpoint parameters. + /// + /// parameter's name + /// iedmtype of the parameter + /// true to include the parameter name in the path + /// + private static string GetParameterPathAssignment(string parameterName, IEdmTypeReference type, bool isIncludeParamName) + { + Contract.Requires(parameterName != null); + Contract.Requires(type != null); + const string paramNamePrefixFormat = "{0}="; + + string parameterPrefix = String.Empty; + if (isIncludeParamName) + { + parameterPrefix = String.Format(paramNamePrefixFormat, parameterName); + } + + switch (type.Definition.TypeKind) { case EdmTypeKind.Enum: - return parameter.Name + "=" + parameter.Type.FullName() + "\'{" + parameter.Name + "}\',"; + string enumFullName = type.FullName(); + if (IsEnableEnumPrefixFree()) + { + enumFullName = String.Empty; + } + return parameterPrefix + enumFullName + "\'{" + parameterName + "}\',"; default: - return parameter.Name + "=" + "{" + parameter.Name + "},"; + return parameterPrefix + "{" + parameterName + "},"; } } @@ -929,5 +989,25 @@ public static T DeepClone(this T source) where T : class Contract.Assume(result != null); return result; } + + /// + /// Gets the value of the EnumPrefixFree setting in the HttpConfiguration + /// + /// EnumPrefixFree value + private static bool IsEnableEnumPrefixFree() + { + Contract.Requires(HttpConfig != null); + Contract.Ensures(Contract.Result().GetType() == typeof(bool)); + + var odataUriResolverSettings = HttpConfig + .Properties + .Where(prop => + prop.Key.Equals(ResolverSettingsKey)) + .FirstOrDefault().Value; + return Convert.ToBoolean(odataUriResolverSettings + .GetType() + .GetProperty(EnumPrefixFree) + .GetValue(odataUriResolverSettings)); + } } } \ No newline at end of file From f975a1b028f86707d908d004c843c0ccdf44491e Mon Sep 17 00:00:00 2001 From: wchan Date: Thu, 13 Oct 2016 17:09:31 -0700 Subject: [PATCH 06/10] Unit test updated for parameter paths that no longer contain spaces after the ',' --- Swashbuckle.OData.Tests/Fixtures/RestierTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Swashbuckle.OData.Tests/Fixtures/RestierTests.cs b/Swashbuckle.OData.Tests/Fixtures/RestierTests.cs index fb1c322..0d9d682 100644 --- a/Swashbuckle.OData.Tests/Fixtures/RestierTests.cs +++ b/Swashbuckle.OData.Tests/Fixtures/RestierTests.cs @@ -173,7 +173,7 @@ public async Task It_supports_entities_with_multiple_keys() // Assert PathItem pathItem; - swaggerDocument.paths.TryGetValue("/restier/OrderDetails(OrderId={OrderId}, ProductId={ProductId})", out pathItem); + swaggerDocument.paths.TryGetValue("/restier/OrderDetails(OrderId={OrderId},ProductId={ProductId})", out pathItem); pathItem.Should().NotBeNull(); var getResponse = pathItem.get.responses.SingleOrDefault(response => response.Key == "200"); getResponse.Should().NotBeNull(); From 27e68bb5fa298e73bf0abeec7231b5154c4c010c Mon Sep 17 00:00:00 2001 From: wchan Date: Thu, 13 Oct 2016 17:10:41 -0700 Subject: [PATCH 07/10] Removal of configuration based loading for the routes. Will make use of unit testing instead. --- .../App_Start/ODataConfig.cs | 89 +++++++------------ .../App_Start/WebApiConfig.cs | 6 +- Swashbuckle.OData.Sample/Web.config | 7 -- Swashbuckle.OData.Tests/app.config | 3 + 4 files changed, 37 insertions(+), 68 deletions(-) diff --git a/Swashbuckle.OData.Sample/App_Start/ODataConfig.cs b/Swashbuckle.OData.Sample/App_Start/ODataConfig.cs index 4f7dcf4..98dab4a 100644 --- a/Swashbuckle.OData.Sample/App_Start/ODataConfig.cs +++ b/Swashbuckle.OData.Sample/App_Start/ODataConfig.cs @@ -13,7 +13,6 @@ using SwashbuckleODataSample.Models; using SwashbuckleODataSample.Repositories; using SwashbuckleODataSample.Versioning; -using System.Web.Configuration; namespace SwashbuckleODataSample { @@ -29,11 +28,7 @@ public static void Register(HttpConfiguration config) private static async void ConfigureRestierOData(HttpConfiguration config) { - if (true == System.Convert.ToBoolean( - WebConfigurationManager.AppSettings["LoadRestierRoutes"])) - { - await config.MapRestierRoute>("RESTierRoute", "restier", new RestierBatchHandler(GlobalConfiguration.DefaultServer)); - } + await config.MapRestierRoute>("RESTierRoute", "restier", new RestierBatchHandler(GlobalConfiguration.DefaultServer)); } private static void ConfigureWebApiOData(HttpConfiguration config) @@ -41,57 +36,39 @@ private static void ConfigureWebApiOData(HttpConfiguration config) var controllerSelector = new ODataVersionControllerSelector(config); config.Services.Replace(typeof(IHttpControllerSelector), controllerSelector); - if (true == System.Convert.ToBoolean( - WebConfigurationManager.AppSettings["LoadVersionedRoutes"])) - { - // Define a versioned route - config.MapODataServiceRoute("V1RouteVersioning", "odata/v1", GetVersionedModel()); - controllerSelector.RouteVersionSuffixMapping.Add("V1RouteVersioning", "V1"); - - // Define a versioned route that doesn't map to any controller - config.MapODataServiceRoute("odata/v2", "odata/v2", GetFakeModel()); - controllerSelector.RouteVersionSuffixMapping.Add("odata/v2", "V2"); - } - - if (true == System.Convert.ToBoolean( - WebConfigurationManager.AppSettings["LoadCustomRoutes"])) - { - // Define a custom route with custom routing conventions - var conventions = ODataRoutingConventions.CreateDefault(); - conventions.Insert(0, new CustomNavigationPropertyRoutingConvention()); - var customODataRoute = config.MapODataServiceRoute("CustomODataRoute", ODataRoutePrefix, GetCustomRouteModel(), batchHandler: null, pathHandler: new DefaultODataPathHandler(), routingConventions: conventions); - config.AddCustomSwaggerRoute(customODataRoute, "/Customers({Id})/Orders") - .Operation(HttpMethod.Post) - .PathParameter("Id") - .BodyParameter("order"); - } - - if (true == System.Convert.ToBoolean( - WebConfigurationManager.AppSettings["LoadRoutesWithFunctions"])) - { - // Define a route to a controller class that contains functions - config.MapODataServiceRoute("FunctionsODataRoute", ODataRoutePrefix, GetFunctionsEdmModel()); - } - - if (true == System.Convert.ToBoolean( - WebConfigurationManager.AppSettings["LoadDefaultRoutes"])) - { - // Define a default non- versioned route(default route should be at the end as a last catch-all) - config.MapODataServiceRoute("DefaultODataRoute", ODataRoutePrefix, GetDefaultModel()); - } - - if (true == System.Convert.ToBoolean( - WebConfigurationManager.AppSettings["LoadEnumRoutes"])) - { - // Define a route with an enum as a key - config.MapODataServiceRoute("EnumODataRoute", + // Define a versioned route + config.MapODataServiceRoute("V1RouteVersioning", "odata/v1", GetVersionedModel()); + controllerSelector.RouteVersionSuffixMapping.Add("V1RouteVersioning", "V1"); + + // Define a versioned route that doesn't map to any controller + config.MapODataServiceRoute("odata/v2", "odata/v2", GetFakeModel()); + controllerSelector.RouteVersionSuffixMapping.Add("odata/v2", "V2"); + + // Define a custom route with custom routing conventions + var conventions = ODataRoutingConventions.CreateDefault(); + conventions.Insert(0, new CustomNavigationPropertyRoutingConvention()); + var customODataRoute = config.MapODataServiceRoute("CustomODataRoute", ODataRoutePrefix, GetCustomRouteModel(), batchHandler: null, pathHandler: new DefaultODataPathHandler(), routingConventions: conventions); + config.AddCustomSwaggerRoute(customODataRoute, "/Customers({Id})/Orders") + .Operation(HttpMethod.Post) + .PathParameter("Id") + .BodyParameter("order"); + + + // Define a route to a controller class that contains functions + config.MapODataServiceRoute("FunctionsODataRoute", ODataRoutePrefix, GetFunctionsEdmModel()); + + // Define a default non- versioned route(default route should be at the end as a last catch-all) + config.MapODataServiceRoute("DefaultODataRoute", ODataRoutePrefix, GetDefaultModel()); + + // Define a route with an enum as a key + config.MapODataServiceRoute("EnumODataRoute", + ODataRoutePrefix, + GetProductWithEnumKeyModel()); + + // Define a route with an enum/int composite key + config.MapODataServiceRoute("EnumIntCompositeODataRoute", ODataRoutePrefix, - GetProductWithEnumKeyModel()); - // Define a route with an enum/int composite key - config.MapODataServiceRoute("EnumIntCompositeODataRoute", - ODataRoutePrefix, - GetProductWithCompositeEnumIntKeyModel()); - } + GetProductWithCompositeEnumIntKeyModel()); } private static IEdmModel GetDefaultModel() diff --git a/Swashbuckle.OData.Sample/App_Start/WebApiConfig.cs b/Swashbuckle.OData.Sample/App_Start/WebApiConfig.cs index 35954b3..86b7ccf 100644 --- a/Swashbuckle.OData.Sample/App_Start/WebApiConfig.cs +++ b/Swashbuckle.OData.Sample/App_Start/WebApiConfig.cs @@ -13,11 +13,7 @@ public static void Register(HttpConfiguration config) config.EnableEnumPrefixFree(isPrefixFreeEnabled); config.MapHttpAttributeRoutes(); - if (true == System.Convert.ToBoolean( - WebConfigurationManager.AppSettings["LoadDefaultApiRoutes"])) - { - config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}", new { id = RouteParameter.Optional }); - } + config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}", new { id = RouteParameter.Optional }); } } } \ No newline at end of file diff --git a/Swashbuckle.OData.Sample/Web.config b/Swashbuckle.OData.Sample/Web.config index 21e5cbf..bf59a52 100644 --- a/Swashbuckle.OData.Sample/Web.config +++ b/Swashbuckle.OData.Sample/Web.config @@ -66,12 +66,5 @@ - - - - - - - \ No newline at end of file diff --git a/Swashbuckle.OData.Tests/app.config b/Swashbuckle.OData.Tests/app.config index 5f57d41..f150789 100644 --- a/Swashbuckle.OData.Tests/app.config +++ b/Swashbuckle.OData.Tests/app.config @@ -54,4 +54,7 @@ + + + \ No newline at end of file From db3bfadd4cabb3afc119817568af54d362f72f93 Mon Sep 17 00:00:00 2001 From: wchan Date: Thu, 13 Oct 2016 22:57:15 -0700 Subject: [PATCH 08/10] Unit tests added for enum as key issue. --- .../Fixtures/ParameterKeyTests.cs | 338 ++++++++++++++++++ .../Swashbuckle.OData.Tests.csproj | 1 + 2 files changed, 339 insertions(+) create mode 100644 Swashbuckle.OData.Tests/Fixtures/ParameterKeyTests.cs diff --git a/Swashbuckle.OData.Tests/Fixtures/ParameterKeyTests.cs b/Swashbuckle.OData.Tests/Fixtures/ParameterKeyTests.cs new file mode 100644 index 0000000..2942db7 --- /dev/null +++ b/Swashbuckle.OData.Tests/Fixtures/ParameterKeyTests.cs @@ -0,0 +1,338 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; +using System.Web.Http; +using System.Web.OData; +using System.Web.OData.Builder; +using System.Web.OData.Extensions; +using FluentAssertions; +using Microsoft.OData.Edm; +using Microsoft.Owin.Hosting; +using NUnit.Framework; +using Owin; +using Swashbuckle.Swagger; +using SwashbuckleODataSample.Models; + + +namespace Swashbuckle.OData.Tests +{ + [TestFixture] + public class ParameterKeyTests + { + private const string ODataRoutePrefix = "odata"; + private const string EnumKeyEndpointName = "ProductWithEnumKeysTest"; + private const string CompositeKeyEndpointName = "ProductWithCompositeEnumIntKeysTest"; + + private const string EnumNamespace = "SwashbuckleODataSample.Models.MyEnum"; + private const string EnumKeyName = "enumValue"; + private const string IdKeyName = "id"; + + [Test] + public async Task It_supports_enum_key_issue_108() + { + using (WebApp.Start(HttpClientUtils.BaseAddress, + appBuilder => ConfigurationEnumKey(appBuilder, true)) + ) + { + // Arrange + var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress); + + // Verify that the OData route in the test controller is valid + var response = await httpClient.GetAsync($"/{ODataRoutePrefix}/{EnumKeyEndpointName}"); + await response.ValidateSuccessAsync(); + + // Act + var swaggerDocument = await httpClient.GetJsonAsync("swagger/docs/v1"); + + // Assert + PathItem pathItem; + var testEndpoint = $"/{ODataRoutePrefix}/{EnumKeyEndpointName}('{{{EnumKeyName}}}')"; + swaggerDocument.paths.TryGetValue(testEndpoint, out pathItem); + pathItem.Should().NotBeNull(); + + // Assert Enum Pararemter + var enumParamter = pathItem?.@get.parameters.SingleOrDefault(p => p.name == EnumKeyName); + enumParamter.Should().NotBeNull(); + enumParamter?.@enum.Should().NotBeEmpty(); + enumParamter?.@in.Should().Be("path"); + enumParamter?.@type.Should().Be("string"); + enumParamter?.required.ShouldBeEquivalentTo(true); + + await ValidationUtils.ValidateSwaggerJson(); + } + } + + [Test] + public async Task It_supports_enum_as_key_with_enum_prefix_issue_108() + { + using (WebApp.Start(HttpClientUtils.BaseAddress, + appBuilder => ConfigurationEnumKey(appBuilder, false)) + ) + { + // Arrange + var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress); + + // Verify that the OData route in the test controller is valid + var response = await httpClient.GetAsync($"/{ODataRoutePrefix}/{EnumKeyEndpointName}"); + await response.ValidateSuccessAsync(); + + // Act + var swaggerDocument = await httpClient.GetJsonAsync("swagger/docs/v1"); + + // Assert + PathItem pathItem; + var testEndpoint = $"/{ODataRoutePrefix}/{EnumKeyEndpointName}({EnumNamespace}'{{{EnumKeyName}}}')"; + swaggerDocument.paths.TryGetValue(testEndpoint, out pathItem); + pathItem.Should().NotBeNull(); + + await ValidationUtils.ValidateSwaggerJson(); + } + } + + [Test] + public async Task It_supports_composite_key_with_enum_issue_108() + { + using (WebApp.Start(HttpClientUtils.BaseAddress, + appBuilder => ConfigurationCompositeKey(appBuilder, true)) + ) + { + // Arrange + var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress); + + // Verify that the OData route in the test controller is valid + var response = await httpClient.GetAsync($"/{ODataRoutePrefix}/{CompositeKeyEndpointName}"); + await response.ValidateSuccessAsync(); + + // Act + var swaggerDocument = await httpClient.GetJsonAsync("swagger/docs/v1"); + + // Assert + PathItem pathItem; + var testEndpoint = $"/{ODataRoutePrefix}/{CompositeKeyEndpointName}({IdKeyName}={{{IdKeyName}}},{EnumKeyName}='{{{EnumKeyName}}}')"; + swaggerDocument.paths.TryGetValue(testEndpoint, out pathItem); + pathItem.Should().NotBeNull(); + + // Assert Enum Pararemter + var enumParamter = pathItem?.@get.parameters.SingleOrDefault(p => p.name == EnumKeyName); + enumParamter.Should().NotBeNull(); + enumParamter?.@enum.Should().NotBeEmpty(); + enumParamter?.@in.Should().Be("path"); + enumParamter?.@type.Should().Be("string"); + enumParamter?.required.ShouldBeEquivalentTo(true); + + // Assert Id Pararemter + var idParamter = pathItem?.@get.parameters.SingleOrDefault(p => p.name == IdKeyName); + idParamter.Should().NotBeNull(); + idParamter?.@in.Should().Be("path"); + idParamter?.@type.Should().Be("integer"); + idParamter?.required.ShouldBeEquivalentTo(true); + + await ValidationUtils.ValidateSwaggerJson(); + } + } + + [Test] + public async Task It_supports_composite_key_with_enum_no_enum_prefix_issue_108() + { + using (WebApp.Start(HttpClientUtils.BaseAddress, + appBuilder => ConfigurationCompositeKey(appBuilder, false)) + ) + { + // Arrange + var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress); + + // Verify that the OData route in the test controller is valid + var response = await httpClient.GetAsync($"/{ODataRoutePrefix}/{CompositeKeyEndpointName}"); + await response.ValidateSuccessAsync(); + + // Act + var swaggerDocument = await httpClient.GetJsonAsync("swagger/docs/v1"); + + // Assert + PathItem pathItem; + var testEndpoint = $"/{ODataRoutePrefix}/{CompositeKeyEndpointName}({IdKeyName}={{{IdKeyName}}},{EnumKeyName}={EnumNamespace}'{{{EnumKeyName}}}')"; + swaggerDocument.paths.TryGetValue(testEndpoint, out pathItem); + pathItem.Should().NotBeNull(); + + await ValidationUtils.ValidateSwaggerJson(); + } + } + + private static void ConfigurationEnumKey(IAppBuilder appBuilder, bool isEnumPrefixFree) + { + var config = appBuilder.GetStandardHttpConfig(typeof(ProductWithEnumKeysTestController)); + config.EnableEnumPrefixFree(isEnumPrefixFree); + + config.MapODataServiceRoute("EnumKeyODataRoute", + ODataRoutePrefix, + GetProductWithEnumKeyModel()); + + config.EnsureInitialized(); + } + + private static void ConfigurationCompositeKey(IAppBuilder appBuilder, bool isEnumPrefixFree) + { + var config = appBuilder.GetStandardHttpConfig(typeof(ProductWithCompositeEnumIntKeysTestController)); + config.EnableEnumPrefixFree(isEnumPrefixFree); + + config.MapODataServiceRoute("CompositeKeyODataRoute", + ODataRoutePrefix, + GetProductWithCompositeEnumIntKeyModel()); + + config.EnsureInitialized(); + } + + private static IEdmModel GetProductWithEnumKeyModel() + { + var builder = new ODataConventionModelBuilder(); + builder.EnableLowerCamelCase(); + + builder.EntitySet("ProductWithEnumKeysTest"); + + return builder.GetEdmModel(); + } + + private static IEdmModel GetProductWithCompositeEnumIntKeyModel() + { + var builder = new ODataConventionModelBuilder(); + builder.EnableLowerCamelCase(); + + builder.EntitySet + ("ProductWithCompositeEnumIntKeysTest"); + + return builder.GetEdmModel(); + } + } + + #region Models + public class ProductWithEnumKey + { + [Key] + public SwashbuckleODataSample.Models.MyEnum EnumValue { get; set; } + + public string Name { get; set; } + + public double Price { get; set; } + } + + public class ProductWithCompositeEnumIntKey + { + [Key] + public MyEnum EnumValue { get; set; } + + [Key] + public int Id { get; set; } + + public string Name { get; set; } + + public double Price { get; set; } + } + #endregion + + #region ODataControllers + public class ProductWithEnumKeysTestController : ODataController + { + private static readonly Dictionary DataEnumAsKey; + + static ProductWithEnumKeysTestController() + { + DataEnumAsKey = new Dictionary() + { + { + MyEnum.ValueOne, + new ProductWithEnumKey { + EnumValue = MyEnum.ValueOne, + Name = "ValueOneName", + Price = 101 + } + }, + { + MyEnum.ValueTwo, + new ProductWithEnumKey + { + EnumValue = MyEnum.ValueTwo, + Name = "ValueTwoName", + Price = 102 + } + } + }; + } + + /// + /// Query products + /// + [HttpGet] + [EnableQuery] + public IQueryable Get() + { + return DataEnumAsKey.Values.AsQueryable(); + } + + /// + /// Query product by enum key + /// + /// key enum value + /// project enum model + [HttpGet] + public IHttpActionResult Get([FromODataUri]MyEnum Key) + { + return Ok(DataEnumAsKey[Key]); + } + } + + public class ProductWithCompositeEnumIntKeysTestController : ODataController + { + private static readonly List DataCompositeKey; + + static ProductWithCompositeEnumIntKeysTestController() + { + DataCompositeKey = new List() + { + { + new ProductWithCompositeEnumIntKey + { + EnumValue = MyEnum.ValueOne, + Id = 1, + Name = "ValueOneName", + Price = 101 + } + }, + { + new ProductWithCompositeEnumIntKey + { + EnumValue = MyEnum.ValueTwo, + Id = 2, + Name = "ValueTwoName", + Price = 102 + } + } + }; + } + + /// + /// Query products + /// + [EnableQuery] + public IQueryable Get() + { + return DataCompositeKey.AsQueryable(); + } + + /// + /// Query products by keys + /// + /// key enum value + /// key id + /// composite enum-int key model + [EnableQuery] + public IHttpActionResult Get([FromODataUri]MyEnum keyenumValue, [FromODataUri]int keyid) + { + return Ok(DataCompositeKey + .Where(x => x.EnumValue == keyenumValue + && x.Id == keyid)); + } + } + #endregion +} \ No newline at end of file diff --git a/Swashbuckle.OData.Tests/Swashbuckle.OData.Tests.csproj b/Swashbuckle.OData.Tests/Swashbuckle.OData.Tests.csproj index 031c1e2..674e55e 100644 --- a/Swashbuckle.OData.Tests/Swashbuckle.OData.Tests.csproj +++ b/Swashbuckle.OData.Tests/Swashbuckle.OData.Tests.csproj @@ -167,6 +167,7 @@ + From 975514e0c8085dc2123c72b1a39b892e3647da02 Mon Sep 17 00:00:00 2001 From: wchan Date: Thu, 13 Oct 2016 23:01:21 -0700 Subject: [PATCH 09/10] Removed EnableEnumPrefixFree appsetting from Tests. --- Swashbuckle.OData.Tests/app.config | 3 --- 1 file changed, 3 deletions(-) diff --git a/Swashbuckle.OData.Tests/app.config b/Swashbuckle.OData.Tests/app.config index f150789..5f57d41 100644 --- a/Swashbuckle.OData.Tests/app.config +++ b/Swashbuckle.OData.Tests/app.config @@ -54,7 +54,4 @@ - - - \ No newline at end of file From 58f3a75023b7b098538e83c367ba755910550086 Mon Sep 17 00:00:00 2001 From: wchan Date: Thu, 13 Oct 2016 23:07:21 -0700 Subject: [PATCH 10/10] Tidy up extra newline. --- Swashbuckle.OData.Sample/App_Start/ODataConfig.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Swashbuckle.OData.Sample/App_Start/ODataConfig.cs b/Swashbuckle.OData.Sample/App_Start/ODataConfig.cs index 98dab4a..847f3e0 100644 --- a/Swashbuckle.OData.Sample/App_Start/ODataConfig.cs +++ b/Swashbuckle.OData.Sample/App_Start/ODataConfig.cs @@ -52,7 +52,6 @@ private static void ConfigureWebApiOData(HttpConfiguration config) .Operation(HttpMethod.Post) .PathParameter("Id") .BodyParameter("order"); - // Define a route to a controller class that contains functions config.MapODataServiceRoute("FunctionsODataRoute", ODataRoutePrefix, GetFunctionsEdmModel());