From c498a750bca6f799ce6be1e18698fc9df02d6bfd Mon Sep 17 00:00:00 2001 From: Richard Beauchamp Date: Sun, 7 Feb 2016 20:22:33 -0800 Subject: [PATCH] Supports route prefixes that contain parameters. Closes #57. --- README.md | 26 ++ .../Swashbuckle.OData.NuGet.nuproj | 4 +- .../Fixtures/ParameterizedRoutePrefixTests.cs | 433 ++++++++++++++++++ .../Swashbuckle.OData.Tests.csproj | 1 + .../EntityDataModelRouteGenerator.cs | 10 +- .../Descriptions/ODataSwaggerUtilities.cs | 143 ++++-- .../Descriptions/OperationExtensions.cs | 2 +- .../Descriptions/ParameterExtensions.cs | 6 +- .../Descriptions/SwaggerRoute.cs | 11 + .../Descriptions/SwaggerRouteStrategy.cs | 46 +- .../HttpConfigurationExtensions.cs | 7 +- Swashbuckle.OData/Properties/AssemblyInfo.cs | 2 +- appveyor.yml | 2 +- 13 files changed, 625 insertions(+), 68 deletions(-) create mode 100644 Swashbuckle.OData.Tests/Fixtures/ParameterizedRoutePrefixTests.cs diff --git a/README.md b/README.md index bd6153d..98926b3 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,32 @@ config.AddCustomSwaggerRoute(restierRoute, "/Customers({CustomerId})/Orders({Ord .PathParameter("OrderId"); ``` +### Route prefixes that have parameters ### + +The follow snippet demonstrates how to configure route prefixes that have parameters: + +```csharp +// For example, if you have a route prefix with a parameter "tenantId" of type long +var odataRoute = config.MapODataServiceRoute("odata", "odata/{tenantId}", builder.GetEdmModel()); + +// Then add the following route constraint so that Swashbuckle.OData knows the parameter type. +// If you don't add this line then the parameter will be assumed to be of type string. +odataRoute.Constraints.Add("tenantId", new LongRouteConstraint()); +``` +Swashbuckle.OData supports the following route constraints: + +| Parameter Type | Route Constraint | +|----------------|---------------------------| +| `bool` | `BoolRouteConstraint` | +| `DateTime` | `DateTimeRouteConstraint` | +| `decimal` | `DecimalRouteConstraint` | +| `double` | `DoubleRouteConstraint` | +| `float` | `FloatRouteConstraint` | +| `Guid` | `GuidRouteConstraint` | +| `int` | `IntRouteConstraint` | +| `long` | `LongRouteConstraint` | + + ### OWIN ### If your service is hosted using OWIN middleware, configure the custom provider as follows: diff --git a/Swashbuckle.OData.Nuget/Swashbuckle.OData.NuGet.nuproj b/Swashbuckle.OData.Nuget/Swashbuckle.OData.NuGet.nuproj index d0d22a6..91cc3b5 100644 --- a/Swashbuckle.OData.Nuget/Swashbuckle.OData.NuGet.nuproj +++ b/Swashbuckle.OData.Nuget/Swashbuckle.OData.NuGet.nuproj @@ -25,12 +25,12 @@ Richard Beauchamp Extends Swashbuckle with OData v4 support! Extends Swashbuckle with OData v4 support! - Supports decimal parameters, keys and return types. + Supports route prefixes that contain parameters. https://github.com/rbeauchamp/Swashbuckle.OData https://github.com/rbeauchamp/Swashbuckle.OData/blob/master/License.txt Copyright 2015 Swashbuckle Swagger SwaggerUi OData Documentation Discovery Help WebApi AspNet AspNetWebApi Docs WebHost IIS - 2.13.0 + 2.14.0 diff --git a/Swashbuckle.OData.Tests/Fixtures/ParameterizedRoutePrefixTests.cs b/Swashbuckle.OData.Tests/Fixtures/ParameterizedRoutePrefixTests.cs new file mode 100644 index 0000000..0243acc --- /dev/null +++ b/Swashbuckle.OData.Tests/Fixtures/ParameterizedRoutePrefixTests.cs @@ -0,0 +1,433 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; +using System.Web.Http; +using System.Web.Http.Routing.Constraints; +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; + +namespace Swashbuckle.OData.Tests +{ + [TestFixture] + public class ParameterizedRoutePrefixTests + { + [Test] + public async Task It_supports_parameterized_route_prefixes_of_type_long() + { + Action configAction = config => + { + var longRoute = config.MapODataServiceRoute("longParam", "odata/{longParam}", GetEdmModel()); + longRoute.Constraints.Add("longParam", new LongRouteConstraint()); + }; + + using (WebApp.Start(HttpClientUtils.BaseAddress, appBuilder => Configuration(appBuilder, typeof(RoutesController), configAction))) + { + // Arrange + var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress); + // Verify that the OData route in the test controller is valid + var oDataResponse = await httpClient.GetJsonAsync>>("/odata/2147483648/Routes"); + oDataResponse.Value.Should().NotBeNull(); + oDataResponse.Value.Count.Should().Be(20); + + // Act + var swaggerDocument = await httpClient.GetJsonAsync("swagger/docs/v1"); + + // Assert + PathItem pathItem; + swaggerDocument.paths.TryGetValue("/odata/{longParam}/Routes", out pathItem); + pathItem.get.Should().NotBeNull(); + + await ValidationUtils.ValidateSwaggerJson(); + } + } + + [Test] + public async Task It_supports_parameterized_route_prefixes_of_type_bool() + { + Action configAction = config => + { + var boolRoute = config.MapODataServiceRoute("boolParam", "odata/{boolParam}", GetEdmModel()); + boolRoute.Constraints.Add("boolParam", new BoolRouteConstraint()); + }; + + using (WebApp.Start(HttpClientUtils.BaseAddress, appBuilder => Configuration(appBuilder, typeof(RoutesController), configAction))) + { + // Arrange + var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress); + // Verify that the OData route in the test controller is valid + var oDataResponse = await httpClient.GetJsonAsync>>("/odata/true/Routes"); + oDataResponse.Value.Should().NotBeNull(); + oDataResponse.Value.Count.Should().Be(20); + + // Act + var swaggerDocument = await httpClient.GetJsonAsync("swagger/docs/v1"); + + // Assert + PathItem pathItem; + swaggerDocument.paths.TryGetValue("/odata/{boolParam}/Routes", out pathItem); + pathItem.get.Should().NotBeNull(); + + await ValidationUtils.ValidateSwaggerJson(); + } + } + + [Test] + public async Task It_supports_parameterized_route_prefixes_of_type_dateTime() + { + Action configAction = config => + { + var dateTimeRoute = config.MapODataServiceRoute("dateTimeParam", "odata/{dateTimeParam}", GetEdmModel()); + dateTimeRoute.Constraints.Add("dateTimeParam", new DateTimeRouteConstraint()); + }; + + using (WebApp.Start(HttpClientUtils.BaseAddress, appBuilder => Configuration(appBuilder, typeof(RoutesController), configAction))) + { + // Arrange + var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress); + // Verify that the OData route in the test controller is valid + var oDataResponse = await httpClient.GetJsonAsync>>("/odata/2015-10-10T17:00:00Z/Routes"); + oDataResponse.Value.Should().NotBeNull(); + oDataResponse.Value.Count.Should().Be(20); + + // Act + var swaggerDocument = await httpClient.GetJsonAsync("swagger/docs/v1"); + + // Assert + PathItem pathItem; + swaggerDocument.paths.TryGetValue("/odata/{dateTimeParam}/Routes", out pathItem); + pathItem.get.Should().NotBeNull(); + + await ValidationUtils.ValidateSwaggerJson(); + } + } + + [Test] + public async Task It_supports_parameterized_route_prefixes_of_type_decimal() + { + Action configAction = config => + { + var decimalRoute = config.MapODataServiceRoute("decimalParam", "odata/{decimalParam}", GetEdmModel()); + decimalRoute.Constraints.Add("decimalParam", new DecimalRouteConstraint()); + }; + + using (WebApp.Start(HttpClientUtils.BaseAddress, appBuilder => Configuration(appBuilder, typeof(RoutesController), configAction))) + { + // Arrange + var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress); + // Verify that the OData route in the test controller is valid + var oDataResponse = await httpClient.GetJsonAsync>>("/odata/1.12/Routes"); + oDataResponse.Value.Should().NotBeNull(); + oDataResponse.Value.Count.Should().Be(20); + + // Act + var swaggerDocument = await httpClient.GetJsonAsync("swagger/docs/v1"); + + // Assert + PathItem pathItem; + swaggerDocument.paths.TryGetValue("/odata/{decimalParam}/Routes", out pathItem); + pathItem.get.Should().NotBeNull(); + + await ValidationUtils.ValidateSwaggerJson(); + } + } + + [Test] + public async Task It_supports_parameterized_route_prefixes_of_type_double() + { + Action configAction = config => + { + var doubleRoute = config.MapODataServiceRoute("doubleParam", "odata/{doubleParam}", GetEdmModel()); + doubleRoute.Constraints.Add("doubleParam", new DoubleRouteConstraint()); + }; + + using (WebApp.Start(HttpClientUtils.BaseAddress, appBuilder => Configuration(appBuilder, typeof(RoutesController), configAction))) + { + // Arrange + var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress); + // Verify that the OData route in the test controller is valid + var oDataResponse = await httpClient.GetJsonAsync>>("/odata/2.34/Routes"); + oDataResponse.Value.Should().NotBeNull(); + oDataResponse.Value.Count.Should().Be(20); + + // Act + var swaggerDocument = await httpClient.GetJsonAsync("swagger/docs/v1"); + + // Assert + PathItem pathItem; + swaggerDocument.paths.TryGetValue("/odata/{doubleParam}/Routes", out pathItem); + pathItem.get.Should().NotBeNull(); + + await ValidationUtils.ValidateSwaggerJson(); + } + } + + [Test] + public async Task It_supports_parameterized_route_prefixes_of_type_float() + { + Action configAction = config => + { + var floatRoute = config.MapODataServiceRoute("floatParam", "odata/{floatParam}", GetEdmModel()); + floatRoute.Constraints.Add("floatParam", new FloatRouteConstraint()); + }; + + using (WebApp.Start(HttpClientUtils.BaseAddress, appBuilder => Configuration(appBuilder, typeof(RoutesController), configAction))) + { + // Arrange + var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress); + // Verify that the OData route in the test controller is valid + var oDataResponse = await httpClient.GetJsonAsync>>("/odata/2.34/Routes"); + oDataResponse.Value.Should().NotBeNull(); + oDataResponse.Value.Count.Should().Be(20); + + // Act + var swaggerDocument = await httpClient.GetJsonAsync("swagger/docs/v1"); + + // Assert + PathItem pathItem; + swaggerDocument.paths.TryGetValue("/odata/{floatParam}/Routes", out pathItem); + pathItem.get.Should().NotBeNull(); + + await ValidationUtils.ValidateSwaggerJson(); + } + } + + [Test] + public async Task It_supports_parameterized_route_prefixes_of_type_guid() + { + Action configAction = config => + { + var guidRoute = config.MapODataServiceRoute("guidParam", "odata/{guidParam}", GetEdmModel()); + guidRoute.Constraints.Add("guidParam", new GuidRouteConstraint()); + }; + + using (WebApp.Start(HttpClientUtils.BaseAddress, appBuilder => Configuration(appBuilder, typeof(RoutesController), configAction))) + { + // Arrange + var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress); + // Verify that the OData route in the test controller is valid + var oDataResponse = await httpClient.GetJsonAsync>>("/odata/8b3434cb-112e-494d-82d5-17021c928012/Routes"); + oDataResponse.Value.Should().NotBeNull(); + oDataResponse.Value.Count.Should().Be(20); + + // Act + var swaggerDocument = await httpClient.GetJsonAsync("swagger/docs/v1"); + + // Assert + PathItem pathItem; + swaggerDocument.paths.TryGetValue("/odata/{guidParam}/Routes", out pathItem); + pathItem.get.Should().NotBeNull(); + + await ValidationUtils.ValidateSwaggerJson(); + } + } + + [Test] + public async Task It_supports_parameterized_route_prefixes_of_type_int() + { + Action configAction = config => + { + var intRoute = config.MapODataServiceRoute("intParam", "odata/{intParam}", GetEdmModel()); + intRoute.Constraints.Add("intParam", new IntRouteConstraint()); + }; + + using (WebApp.Start(HttpClientUtils.BaseAddress, appBuilder => Configuration(appBuilder, typeof(RoutesController), configAction))) + { + // Arrange + var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress); + // Verify that the OData route in the test controller is valid + var oDataResponse = await httpClient.GetJsonAsync>>("/odata/45/Routes"); + oDataResponse.Value.Should().NotBeNull(); + oDataResponse.Value.Count.Should().Be(20); + + // Act + var swaggerDocument = await httpClient.GetJsonAsync("swagger/docs/v1"); + + // Assert + PathItem pathItem; + swaggerDocument.paths.TryGetValue("/odata/{intParam}/Routes", out pathItem); + pathItem.get.Should().NotBeNull(); + + await ValidationUtils.ValidateSwaggerJson(); + } + } + + [Test] + public async Task It_supports_route_prefixes_with_multiple_parameters() + { + Action configAction = config => + { + var intRoute = config.MapODataServiceRoute("multiParam", "odata/{intParam}/{boolParam}", GetEdmModel()); + intRoute.Constraints.Add("intParam", new IntRouteConstraint()); + intRoute.Constraints.Add("boolParam", new BoolRouteConstraint()); + }; + + using (WebApp.Start(HttpClientUtils.BaseAddress, appBuilder => Configuration(appBuilder, typeof(RoutesController), configAction))) + { + // Arrange + var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress); + // Verify that the OData route in the test controller is valid + var oDataResponse = await httpClient.GetJsonAsync>>("/odata/45/true/Routes"); + oDataResponse.Value.Should().NotBeNull(); + oDataResponse.Value.Count.Should().Be(20); + + // Act + var swaggerDocument = await httpClient.GetJsonAsync("swagger/docs/v1"); + + // Assert + PathItem pathItem; + swaggerDocument.paths.TryGetValue("/odata/{intParam}/{boolParam}/Routes", out pathItem); + pathItem.get.Should().NotBeNull(); + + await ValidationUtils.ValidateSwaggerJson(); + } + } + + [Test] + public async Task It_supports_parameterized_route_prefixes_of_type_string() + { + Action configAction = config => + { + config.MapODataServiceRoute("stringParam", "odata/{stringParam}", GetEdmModel()); + }; + + using (WebApp.Start(HttpClientUtils.BaseAddress, appBuilder => Configuration(appBuilder, typeof(RoutesController), configAction))) + { + // Arrange + var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress); + // Verify that the OData route in the test controller is valid + var oDataResponse = await httpClient.GetJsonAsync>>("/odata/'foo'/Routes"); + oDataResponse.Value.Should().NotBeNull(); + oDataResponse.Value.Count.Should().Be(20); + + // Act + var swaggerDocument = await httpClient.GetJsonAsync("swagger/docs/v1"); + + // Assert + PathItem pathItem; + swaggerDocument.paths.TryGetValue("/odata/'{stringParam}'/Routes", out pathItem); + pathItem.get.Should().NotBeNull(); + + await ValidationUtils.ValidateSwaggerJson(); + } + } + + private static void Configuration(IAppBuilder appBuilder, Type targetController, Action configAction) + { + var config = appBuilder.GetStandardHttpConfig(targetController); + + configAction(config); + + config.EnsureInitialized(); + } + + private static IEdmModel GetEdmModel() + { + ODataModelBuilder builder = new ODataConventionModelBuilder(); + + builder.EntitySet("Routes"); + + return builder.GetEdmModel(); + } + } + + public class Route + { + [Key] + public string Id { get; set; } + } + + public class RoutesController : ODataController + { + private static readonly ConcurrentDictionary Data; + + static RoutesController() + { + Data = new ConcurrentDictionary(); + + Enumerable.Range(0, 20).Select(i => new Route + { + Id = Guid.NewGuid().ToString() + }).ToList().ForEach(p => Data.TryAdd(p.Id, p)); + } + + [HttpGet] + [EnableQuery] + public IQueryable GetRoutes(long longParam) + { + return Data.Values.AsQueryable(); + } + + [HttpGet] + [EnableQuery] + public IQueryable GetRoutes(bool boolParam) + { + return Data.Values.AsQueryable(); + } + + [HttpGet] + [EnableQuery] + public IQueryable GetRoutes(DateTime dateTimeParam) + { + return Data.Values.AsQueryable(); + } + + [HttpGet] + [EnableQuery] + public IQueryable GetRoutes(decimal decimalParam) + { + return Data.Values.AsQueryable(); + } + + [HttpGet] + [EnableQuery] + public IQueryable GetRoutes(double doubleParam) + { + return Data.Values.AsQueryable(); + } + + [HttpGet] + [EnableQuery] + public IQueryable GetRoutes(float floatParam) + { + return Data.Values.AsQueryable(); + } + + [HttpGet] + [EnableQuery] + public IQueryable GetRoutes(Guid guidParam) + { + return Data.Values.AsQueryable(); + } + + [HttpGet] + [EnableQuery] + public IQueryable GetRoutes(int intParam) + { + return Data.Values.AsQueryable(); + } + + [HttpGet] + [EnableQuery] + public IQueryable GetRoutes(string stringParam) + { + return Data.Values.AsQueryable(); + } + + [HttpGet] + [EnableQuery] + public IQueryable GetRoutes(int intParam, bool boolParam) + { + return Data.Values.AsQueryable(); + } + } +} \ 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 c98f53e..bab9eee 100644 --- a/Swashbuckle.OData.Tests/Swashbuckle.OData.Tests.csproj +++ b/Swashbuckle.OData.Tests/Swashbuckle.OData.Tests.csproj @@ -147,6 +147,7 @@ + diff --git a/Swashbuckle.OData/Descriptions/EntityDataModelRouteGenerator.cs b/Swashbuckle.OData/Descriptions/EntityDataModelRouteGenerator.cs index f15cb57..31dd208 100644 --- a/Swashbuckle.OData/Descriptions/EntityDataModelRouteGenerator.cs +++ b/Swashbuckle.OData/Descriptions/EntityDataModelRouteGenerator.cs @@ -45,7 +45,7 @@ private static IEnumerable GenerateEntitySetRoutes(ODataRoute oDat return oDataRoute.GetEdmModel() .EntityContainer .EntitySets()? - .Select(entitySet => new SwaggerRoute(ODataSwaggerUtilities.GetPathForEntitySet(oDataRoute.GetRoutePrefix(), entitySet), oDataRoute, ODataSwaggerUtilities.CreateSwaggerPathForEntitySet(entitySet))); + .Select(entitySet => new SwaggerRoute(ODataSwaggerUtilities.GetPathForEntitySet(entitySet), oDataRoute, ODataSwaggerUtilities.CreateSwaggerPathForEntitySet(entitySet, oDataRoute))); } private static IEnumerable GenerateEntityRoutes(ODataRoute oDataRoute) @@ -56,7 +56,7 @@ private static IEnumerable GenerateEntityRoutes(ODataRoute oDataRo return oDataRoute.GetEdmModel() .EntityContainer .EntitySets()? - .Select(entitySet => new SwaggerRoute(ODataSwaggerUtilities.GetPathForEntity(oDataRoute.GetRoutePrefix(), entitySet), oDataRoute, ODataSwaggerUtilities.CreateSwaggerPathForEntity(entitySet))); + .Select(entitySet => new SwaggerRoute(ODataSwaggerUtilities.GetPathForEntity(entitySet), oDataRoute, ODataSwaggerUtilities.CreateSwaggerPathForEntity(entitySet, oDataRoute))); } private static IEnumerable GenerateOperationImportRoutes(ODataRoute oDataRoute) @@ -67,7 +67,7 @@ private static IEnumerable GenerateOperationImportRoutes(ODataRout return oDataRoute.GetEdmModel() .EntityContainer .OperationImports()? - .Select(operationImport => new SwaggerRoute(ODataSwaggerUtilities.GetPathForOperationImport(oDataRoute.GetRoutePrefix(), operationImport), oDataRoute, ODataSwaggerUtilities.CreateSwaggerPathForOperationImport(operationImport))); + .Select(operationImport => new SwaggerRoute(ODataSwaggerUtilities.GetPathForOperationImport(operationImport), oDataRoute, ODataSwaggerUtilities.CreateSwaggerPathForOperationImport(operationImport, oDataRoute))); } /// @@ -107,7 +107,7 @@ private static IEnumerable GenerateOperationRoutes(ODataRoute oDat var entityType = (IEdmEntityType)boundType; var edmEntitySets = oDataRoute.GetEdmModel().EntityContainer.EntitySets(); Contract.Assume(edmEntitySets != null); - routes.AddRange(edmEntitySets.Where(es => es.GetEntityType().Equals(entityType)).Select(entitySet => new SwaggerRoute(ODataSwaggerUtilities.GetPathForOperationOfEntity(oDataRoute.GetRoutePrefix(), operation, entitySet), oDataRoute, ODataSwaggerUtilities.CreateSwaggerPathForOperationOfEntity(operation, entitySet)))); + routes.AddRange(edmEntitySets.Where(es => es.GetEntityType().Equals(entityType)).Select(entitySet => new SwaggerRoute(ODataSwaggerUtilities.GetPathForOperationOfEntity(operation, entitySet), oDataRoute, ODataSwaggerUtilities.CreateSwaggerPathForOperationOfEntity(operation, entitySet)))); } else if (boundType.TypeKind == EdmTypeKind.Collection) { @@ -118,7 +118,7 @@ private static IEnumerable GenerateOperationRoutes(ODataRoute oDat var entityType = (IEdmEntityType)collectionType.ElementType?.GetDefinition(); var edmEntitySets = oDataRoute.GetEdmModel().EntityContainer.EntitySets(); Contract.Assume(edmEntitySets != null); - routes.AddRange(edmEntitySets.Where(es => es.GetEntityType().Equals(entityType)).Select(entitySet => new SwaggerRoute(ODataSwaggerUtilities.GetPathForOperationOfEntitySet(operation, entitySet, oDataRoute.GetRoutePrefix()), oDataRoute, ODataSwaggerUtilities.CreateSwaggerPathForOperationOfEntitySet(operation, entitySet)))); + routes.AddRange(edmEntitySets.Where(es => es.GetEntityType().Equals(entityType)).Select(entitySet => new SwaggerRoute(ODataSwaggerUtilities.GetPathForOperationOfEntitySet(operation, entitySet), oDataRoute, ODataSwaggerUtilities.CreateSwaggerPathForOperationOfEntitySet(operation, entitySet, oDataRoute)))); } } } diff --git a/Swashbuckle.OData/Descriptions/ODataSwaggerUtilities.cs b/Swashbuckle.OData/Descriptions/ODataSwaggerUtilities.cs index e46c9ea..c0ff4bf 100644 --- a/Swashbuckle.OData/Descriptions/ODataSwaggerUtilities.cs +++ b/Swashbuckle.OData/Descriptions/ODataSwaggerUtilities.cs @@ -5,8 +5,10 @@ using System.Collections.Generic; using System.Diagnostics.Contracts; using System.Linq; +using System.Web.Http.Routing; +using System.Web.Http.Routing.Constraints; using System.Web.OData.Formatter; -using Flurl; +using System.Web.OData.Routing; using Microsoft.OData.Edm; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; @@ -23,7 +25,8 @@ internal static class ODataSwaggerUtilities /// Create the Swagger path for the Edm entity set. /// /// The entity set. - public static PathItem CreateSwaggerPathForEntitySet(IEdmEntitySet entitySet) + /// + public static PathItem CreateSwaggerPathForEntitySet(IEdmEntitySet entitySet, ODataRoute oDataRoute) { Contract.Requires(entitySet != null); Contract.Ensures(Contract.Result() != null); @@ -36,6 +39,7 @@ public static PathItem CreateSwaggerPathForEntitySet(IEdmEntitySet entitySet) .Description("Returns the EntitySet " + entitySet.Name) .Tags(entitySet.Name) .Parameters(AddQueryOptionParameters(new List())) + .Parameters(AddRoutePrefixParameters(oDataRoute)) .Responses(new Dictionary().Response("200", "EntitySet " + entitySet.Name, entitySet.Type).DefaultErrorResponse()), post = new Operation() .Summary("Post a new entity to EntitySet " + entitySet.Name) @@ -44,10 +48,88 @@ public static PathItem CreateSwaggerPathForEntitySet(IEdmEntitySet entitySet) .Tags(entitySet.Name) .Parameters(new List() .Parameter(entitySet.GetEntityType().Name, "body", "The entity to post", entitySet.GetEntityType(), true)) + .Parameters(AddRoutePrefixParameters(oDataRoute)) .Responses(new Dictionary().Response("200", "EntitySet " + entitySet.Name, entitySet.GetEntityType()).DefaultErrorResponse()) }; } + private static IList AddRoutePrefixParameters(ODataRoute oDataRoute) + { + Contract.Requires(oDataRoute != null); + var routePrefixParameters = new List(); + var routePrefixTemplate = new UriTemplate(oDataRoute.GetRoutePrefix()); + if (routePrefixTemplate.PathSegmentVariableNames.Any()) + { + routePrefixParameters.AddRange(routePrefixTemplate.PathSegmentVariableNames.Select(pathSegmentVariableName => CreateParameter(pathSegmentVariableName, oDataRoute))); + } + return routePrefixParameters; + } + + private static Parameter CreateParameter(string pathSegmentVariableName, ODataRoute oDataRoute) + { + var parameter = new Parameter + { + name = GetOriginalParameterNameFromRoutePrefix(pathSegmentVariableName, oDataRoute), + @in = "path", + required = true + }; + object routeConstraint; + if (oDataRoute.Constraints.TryGetValue(pathSegmentVariableName, out routeConstraint) && routeConstraint is IHttpRouteConstraint) + { + SetSwaggerType(parameter, (IHttpRouteConstraint)routeConstraint); + } + else + { + SetSwaggerType(parameter, EdmLibHelpers.GetEdmPrimitiveTypeOrNull(typeof(string))); + } + return parameter; + } + + private static string GetOriginalParameterNameFromRoutePrefix(string pathSegmentVariableName, ODataRoute oDataRoute) + { + return oDataRoute.GetRoutePrefix().Substring(oDataRoute.GetRoutePrefix().IndexOf(pathSegmentVariableName, StringComparison.CurrentCultureIgnoreCase), pathSegmentVariableName.Length); + } + + private static void SetSwaggerType(Parameter parameter, IHttpRouteConstraint routeConstraint) + { + if (routeConstraint is BoolRouteConstraint) + { + SetSwaggerType(parameter, EdmLibHelpers.GetEdmPrimitiveTypeOrNull(typeof(bool))); + } + else if (routeConstraint is DateTimeRouteConstraint) + { + SetSwaggerType(parameter, EdmLibHelpers.GetEdmPrimitiveTypeOrNull(typeof(DateTime))); + } + else if (routeConstraint is DecimalRouteConstraint) + { + SetSwaggerType(parameter, EdmLibHelpers.GetEdmPrimitiveTypeOrNull(typeof(decimal))); + } + else if (routeConstraint is DoubleRouteConstraint) + { + SetSwaggerType(parameter, EdmLibHelpers.GetEdmPrimitiveTypeOrNull(typeof(double))); + } + else if (routeConstraint is FloatRouteConstraint) + { + SetSwaggerType(parameter, EdmLibHelpers.GetEdmPrimitiveTypeOrNull(typeof(float))); + } + else if (routeConstraint is GuidRouteConstraint) + { + SetSwaggerType(parameter, EdmLibHelpers.GetEdmPrimitiveTypeOrNull(typeof(Guid))); + } + else if (routeConstraint is IntRouteConstraint) + { + SetSwaggerType(parameter, EdmLibHelpers.GetEdmPrimitiveTypeOrNull(typeof(int))); + } + else if (routeConstraint is LongRouteConstraint) + { + SetSwaggerType(parameter, EdmLibHelpers.GetEdmPrimitiveTypeOrNull(typeof (long))); + } + else + { + SetSwaggerType(parameter, EdmLibHelpers.GetEdmPrimitiveTypeOrNull(typeof(string))); + } + } + public static IList AddQueryOptionParameters(IList parameterList) { return parameterList @@ -64,8 +146,9 @@ public static IList AddQueryOptionParameters(IList paramet /// Create the Swagger path for the Edm entity. /// /// The entity set. + /// /// - public static PathItem CreateSwaggerPathForEntity(IEdmEntitySet entitySet) + public static PathItem CreateSwaggerPathForEntity(IEdmEntitySet entitySet, ODataRoute oDataRoute) { Contract.Requires(entitySet != null); Contract.Ensures(Contract.Result() != null); @@ -91,6 +174,7 @@ public static PathItem CreateSwaggerPathForEntity(IEdmEntitySet entitySet) .Parameters(keyParameters.DeepClone() .Parameter("$expand", "query", "Expands related entities inline.", "string", false) .Parameter("$select", "query", "Selects which properties to include in the response.", "string", false)) + .Parameters(AddRoutePrefixParameters(oDataRoute)) .Responses(new Dictionary().Response("200", "EntitySet " + entitySet.Name, entitySet.GetEntityType()).DefaultErrorResponse()), patch = new Operation() @@ -100,6 +184,7 @@ public static PathItem CreateSwaggerPathForEntity(IEdmEntitySet entitySet) .Tags(entitySet.Name) .Parameters(keyParameters.DeepClone() .Parameter(entitySet.GetEntityType().Name, "body", "The entity to patch", entitySet.GetEntityType(), true)) + .Parameters(AddRoutePrefixParameters(oDataRoute)) .Responses(new Dictionary() .Response("204", "Empty response").DefaultErrorResponse()), @@ -110,6 +195,7 @@ public static PathItem CreateSwaggerPathForEntity(IEdmEntitySet entitySet) .Tags(entitySet.Name) .Parameters(keyParameters.DeepClone() .Parameter(entitySet.GetEntityType().Name, "body", "The entity to put", entitySet.GetEntityType(), true)) + .Parameters(AddRoutePrefixParameters(oDataRoute)) .Responses(new Dictionary().Response("204", "Empty response").DefaultErrorResponse()), delete = new Operation().Summary("Delete entity in EntitySet " + entitySet.Name) @@ -118,6 +204,7 @@ public static PathItem CreateSwaggerPathForEntity(IEdmEntitySet entitySet) .Tags(entitySet.Name) .Parameters(keyParameters.DeepClone() .Parameter("If-Match", "header", "If-Match header", "string", false)) + .Parameters(AddRoutePrefixParameters(oDataRoute)) .Responses(new Dictionary().Response("204", "Empty response").DefaultErrorResponse()) }; } @@ -126,8 +213,9 @@ public static PathItem CreateSwaggerPathForEntity(IEdmEntitySet entitySet) /// Create the Swagger path for the Edm operation import. /// /// The Edm operation import + /// /// The represents the related Edm operation import. - public static PathItem CreateSwaggerPathForOperationImport(IEdmOperationImport operationImport) + public static PathItem CreateSwaggerPathForOperationImport(IEdmOperationImport operationImport, ODataRoute oDataRoute) { Contract.Requires(operationImport != null); @@ -164,6 +252,9 @@ public static PathItem CreateSwaggerPathForOperationImport(IEdmOperationImport o { swaggerOperationImport.Parameters(swaggerParameters); } + + swaggerOperationImport.Parameters(AddRoutePrefixParameters(oDataRoute)); + swaggerOperationImport.Responses(swaggerResponses.DefaultErrorResponse()); return isFunctionImport ? new PathItem @@ -180,7 +271,8 @@ public static PathItem CreateSwaggerPathForOperationImport(IEdmOperationImport o /// /// The Edm operation. /// The entity set. - public static PathItem CreateSwaggerPathForOperationOfEntitySet(IEdmOperation operation, IEdmEntitySet entitySet) + /// + public static PathItem CreateSwaggerPathForOperationOfEntitySet(IEdmOperation operation, IEdmEntitySet entitySet, ODataRoute oDataRoute) { Contract.Requires(operation != null); Contract.Requires(entitySet != null); @@ -210,6 +302,7 @@ public static PathItem CreateSwaggerPathForOperationOfEntitySet(IEdmOperation op .OperationId(operation.Name + (isFunction ? "_FunctionGet" : "_ActionPost")) .Description("Call operation " + operation.Name) .OperationId(operation.Name + (isFunction ? "_FunctionGetById" : "_ActionPostById")) + .Parameters(AddRoutePrefixParameters(oDataRoute)) .Tags(entitySet.Name, isFunction ? "Function" : "Action"); if (swaggerParameters.Count > 0) @@ -344,31 +437,27 @@ public static PathItem CreateSwaggerPathForOperationOfEntity(IEdmOperation opera /// /// Gets the path for entity set. /// - /// The route prefix. /// The entity set. /// - public static Url GetPathForEntitySet(string routePrefix, IEdmEntitySet entitySet) + public static string GetPathForEntitySet(IEdmEntitySet entitySet) { Contract.Requires(entitySet != null); - Contract.Requires(routePrefix != null); - return routePrefix.AppendPathSegment(entitySet.Name); + return entitySet.Name; } /// /// Get the Uri Swagger path for the Edm entity set. /// - /// The route prefix. /// The entity set. /// /// The path represents the related Edm entity set. /// - public static string GetPathForEntity(string routePrefix, IEdmEntitySet entitySet) + public static string GetPathForEntity(IEdmEntitySet entitySet) { - Contract.Requires(routePrefix != null); Contract.Requires(entitySet != null); - var singleEntityPath = GetPathForEntitySet(routePrefix, entitySet) + "("; + var singleEntityPath = GetPathForEntitySet(entitySet) + "("; singleEntityPath = entitySet.GetEntityType().GetKey().Count() == 1 ? AppendSingleColumnKeyTemplate(entitySet, singleEntityPath) : AppendMultiColumnKeyTemplate(entitySet, singleEntityPath); @@ -405,17 +494,15 @@ private static string AppendMultiColumnKeyTemplate(IEdmEntitySet entitySet, stri /// /// Get the Uri Swagger path for Edm operation import. /// - /// The route prefix. /// The Edm operation import. /// /// The path represents the related Edm operation import. /// - public static string GetPathForOperationImport(string routePrefix, IEdmOperationImport operationImport) + public static string GetPathForOperationImport(IEdmOperationImport operationImport) { - Contract.Requires(routePrefix != null); Contract.Requires(operationImport != null); - var swaggerOperationImportPath = routePrefix.AppendPathSegment(operationImport.Name).ToString(); + var swaggerOperationImportPath = operationImport.Name; if (operationImport.IsFunctionImport()) { swaggerOperationImportPath += "("; @@ -439,17 +526,15 @@ public static string GetPathForOperationImport(string routePrefix, IEdmOperation /// /// The Edm operation. /// The entity set. - /// The route prefix. /// /// The path represents the related Edm operation. /// - public static string GetPathForOperationOfEntitySet(IEdmOperation operation, IEdmEntitySet entitySet, string routePrefix) + public static string GetPathForOperationOfEntitySet(IEdmOperation operation, IEdmEntitySet entitySet) { Contract.Requires(operation != null); Contract.Requires(entitySet != null); - Contract.Requires(routePrefix != null); - var swaggerOperationPath = GetPathForEntitySet(routePrefix, entitySet) + "/" + operation.FullName(); + var swaggerOperationPath = GetPathForEntitySet(entitySet) + "/" + operation.FullName(); if (operation.IsFunction()) { swaggerOperationPath += "("; @@ -489,19 +574,17 @@ private static string GetFunctionParameterAssignmentPath(IEdmOperationParameter /// /// Get the Uri Swagger path for Edm operation bound to entity. /// - /// The route prefix. /// The Edm operation. /// The entity set. /// /// The path represents the related Edm operation. /// - public static string GetPathForOperationOfEntity(string routePrefix, IEdmOperation operation, IEdmEntitySet entitySet) + public static string GetPathForOperationOfEntity(IEdmOperation operation, IEdmEntitySet entitySet) { Contract.Requires(operation != null); Contract.Requires(entitySet != null); - Contract.Requires(routePrefix != null); - var swaggerOperationPath = GetPathForEntity(routePrefix, entitySet) + "/" + operation.FullName(); + var swaggerOperationPath = GetPathForEntity(entitySet) + "/" + operation.FullName(); if (operation.IsFunction()) { swaggerOperationPath += "("; @@ -712,12 +795,14 @@ private static IDictionary Response(this IDictionary parameters) + private static Operation Parameters(this Operation operation, IList parameters) { - Contract.Requires(obj != null); + Contract.Requires(operation != null); + Contract.Requires(parameters != null); - obj.parameters = parameters; - return obj; + operation.parameters = operation.parameters?.Concat(parameters).ToList() ?? parameters; + + return operation; } private static IList Parameter(this IList parameters, string name, string kind, string description, string type, bool required, string format = null) diff --git a/Swashbuckle.OData/Descriptions/OperationExtensions.cs b/Swashbuckle.OData/Descriptions/OperationExtensions.cs index 298d51e..7bfd583 100644 --- a/Swashbuckle.OData/Descriptions/OperationExtensions.cs +++ b/Swashbuckle.OData/Descriptions/OperationExtensions.cs @@ -17,7 +17,7 @@ public static IDictionary GenerateSamplePathParameterValues(this .ToDictionary(queryParameter => queryParameter.name, queryParameter => queryParameter.GenerateSamplePathParameterValue()); } - public static string GenerateSampleODataAbsoluteUri(this Operation operation, string serviceRoot, string pathTemplate) + public static string GenerateSampleODataUri(this Operation operation, string serviceRoot, string pathTemplate) { Contract.Requires(operation != null); Contract.Requires(serviceRoot != null); diff --git a/Swashbuckle.OData/Descriptions/ParameterExtensions.cs b/Swashbuckle.OData/Descriptions/ParameterExtensions.cs index 9552b2a..809603b 100644 --- a/Swashbuckle.OData/Descriptions/ParameterExtensions.cs +++ b/Swashbuckle.OData/Descriptions/ParameterExtensions.cs @@ -125,11 +125,11 @@ public static string GenerateSamplePathParameterValue(this Parameter parameter) case "date-time": return "2015-10-10T17:00:00Z"; case "double": - return "2.34d"; + return "2.34"; case "decimal": - return "1.12m"; + return "1.12"; case "float": - return "2.0f"; + return "2.0"; case "guid": return Guid.NewGuid().ToString(); case "binary": diff --git a/Swashbuckle.OData/Descriptions/SwaggerRoute.cs b/Swashbuckle.OData/Descriptions/SwaggerRoute.cs index fb9ef42..36d2c91 100644 --- a/Swashbuckle.OData/Descriptions/SwaggerRoute.cs +++ b/Swashbuckle.OData/Descriptions/SwaggerRoute.cs @@ -1,5 +1,7 @@ using System.Diagnostics.Contracts; +using System.Web; using System.Web.OData.Routing; +using Flurl; using Swashbuckle.Swagger; namespace Swashbuckle.OData.Descriptions @@ -37,6 +39,15 @@ public string Template } } + public string PrefixedTemplate + { + get + { + Contract.Ensures(!string.IsNullOrWhiteSpace(Contract.Result())); + return HttpUtility.UrlDecode(ODataRoute.GetRoutePrefix().AppendPathSegment(_template)); + } + } + public ODataRoute ODataRoute { get diff --git a/Swashbuckle.OData/Descriptions/SwaggerRouteStrategy.cs b/Swashbuckle.OData/Descriptions/SwaggerRouteStrategy.cs index c5fe172..44f5080 100644 --- a/Swashbuckle.OData/Descriptions/SwaggerRouteStrategy.cs +++ b/Swashbuckle.OData/Descriptions/SwaggerRouteStrategy.cs @@ -8,7 +8,6 @@ using System.Web.OData.Extensions; using System.Web.OData.Formatter; using System.Web.OData.Routing; -using Flurl; using Microsoft.OData.Edm; using Swashbuckle.Swagger; @@ -48,23 +47,23 @@ private static IEnumerable GetActionDescriptors(SwaggerRo var oDataActionDescriptors = new List(); - oDataActionDescriptors.AddIfNotNull(GetActionDescriptors(new HttpMethod("DELETE"), potentialSwaggerRoute.PathItem.delete, potentialSwaggerRoute.Template, potentialSwaggerRoute.ODataRoute, httpConfig)); - oDataActionDescriptors.AddIfNotNull(GetActionDescriptors(new HttpMethod("GET"), potentialSwaggerRoute.PathItem.get, potentialSwaggerRoute.Template, potentialSwaggerRoute.ODataRoute, httpConfig)); - oDataActionDescriptors.AddIfNotNull(GetActionDescriptors(new HttpMethod("POST"), potentialSwaggerRoute.PathItem.post, potentialSwaggerRoute.Template, potentialSwaggerRoute.ODataRoute, httpConfig)); - oDataActionDescriptors.AddIfNotNull(GetActionDescriptors(new HttpMethod("PUT"), potentialSwaggerRoute.PathItem.put, potentialSwaggerRoute.Template, potentialSwaggerRoute.ODataRoute, httpConfig)); - oDataActionDescriptors.AddIfNotNull(GetActionDescriptors(new HttpMethod("PATCH"), potentialSwaggerRoute.PathItem.patch, potentialSwaggerRoute.Template, potentialSwaggerRoute.ODataRoute, httpConfig)); + oDataActionDescriptors.AddIfNotNull(GetActionDescriptors(new HttpMethod("DELETE"), potentialSwaggerRoute.PathItem.delete, potentialSwaggerRoute, httpConfig)); + oDataActionDescriptors.AddIfNotNull(GetActionDescriptors(new HttpMethod("GET"), potentialSwaggerRoute.PathItem.get, potentialSwaggerRoute, httpConfig)); + oDataActionDescriptors.AddIfNotNull(GetActionDescriptors(new HttpMethod("POST"), potentialSwaggerRoute.PathItem.post, potentialSwaggerRoute, httpConfig)); + oDataActionDescriptors.AddIfNotNull(GetActionDescriptors(new HttpMethod("PUT"), potentialSwaggerRoute.PathItem.put, potentialSwaggerRoute, httpConfig)); + oDataActionDescriptors.AddIfNotNull(GetActionDescriptors(new HttpMethod("PATCH"), potentialSwaggerRoute.PathItem.patch, potentialSwaggerRoute, httpConfig)); return oDataActionDescriptors; } - private static ODataActionDescriptor GetActionDescriptors(HttpMethod httpMethod, Operation potentialOperation, string potentialPathTemplate, ODataRoute oDataRoute, HttpConfiguration httpConfig) + private static ODataActionDescriptor GetActionDescriptors(HttpMethod httpMethod, Operation potentialOperation, SwaggerRoute potentialSwaggerRoute, HttpConfiguration httpConfig) { Contract.Requires(potentialOperation == null || httpConfig != null); - Contract.Requires(potentialPathTemplate != null); + Contract.Requires(potentialSwaggerRoute != null); if (potentialOperation != null) { - var request = CreateHttpRequestMessage(httpMethod, potentialOperation, potentialPathTemplate, oDataRoute, httpConfig); + var request = CreateHttpRequestMessage(httpMethod, potentialOperation, potentialSwaggerRoute, httpConfig); var actionDescriptor = request.GetHttpActionDescriptor(httpConfig); @@ -72,26 +71,26 @@ private static ODataActionDescriptor GetActionDescriptors(HttpMethod httpMethod, { actionDescriptor = MapForRestierIfNecessary(request, actionDescriptor); - return new ODataActionDescriptor(actionDescriptor, oDataRoute, potentialPathTemplate, request, potentialOperation); + return new ODataActionDescriptor(actionDescriptor, potentialSwaggerRoute.ODataRoute, potentialSwaggerRoute.PrefixedTemplate, request, potentialOperation); } } return null; } - private static HttpRequestMessage CreateHttpRequestMessage(HttpMethod httpMethod, Operation potentialOperation, string potentialPathTemplate, ODataRoute oDataRoute, HttpConfiguration httpConfig) + private static HttpRequestMessage CreateHttpRequestMessage(HttpMethod httpMethod, Operation potentialOperation, SwaggerRoute potentialSwaggerRoute, HttpConfiguration httpConfig) { Contract.Requires(httpConfig != null); - Contract.Requires(oDataRoute != null); + Contract.Requires(potentialSwaggerRoute != null); Contract.Ensures(Contract.Result() != null); - Contract.Assume(oDataRoute.Constraints != null); + Contract.Assume(potentialSwaggerRoute.ODataRoute.Constraints != null); - var oDataAbsoluteUri = potentialOperation.GenerateSampleODataAbsoluteUri(ServiceRoot, potentialPathTemplate); + var oDataAbsoluteUri = potentialOperation.GenerateSampleODataUri(ServiceRoot, potentialSwaggerRoute.PrefixedTemplate); var httpRequestMessage = new HttpRequestMessage(httpMethod, oDataAbsoluteUri); - var odataPath = GenerateSampleODataPath(oDataRoute, oDataAbsoluteUri); + var odataPath = GenerateSampleODataPath(potentialOperation, potentialSwaggerRoute); var requestContext = new HttpRequestContext { @@ -99,7 +98,7 @@ private static HttpRequestMessage CreateHttpRequestMessage(HttpMethod httpMethod }; httpRequestMessage.SetConfiguration(httpConfig); httpRequestMessage.SetRequestContext(requestContext); - + var oDataRoute = potentialSwaggerRoute.ODataRoute; var httpRequestMessageProperties = httpRequestMessage.ODataProperties(); Contract.Assume(httpRequestMessageProperties != null); httpRequestMessageProperties.Model = oDataRoute.GetEdmModel(); @@ -151,19 +150,22 @@ private static bool ReturnsValue(HttpRequestMessage request) return request.Method == HttpMethod.Get || request.Method == HttpMethod.Post; } - private static ODataPath GenerateSampleODataPath(ODataRoute oDataRoute, string sampleODataAbsoluteUri) + private static ODataPath GenerateSampleODataPath(Operation operation, SwaggerRoute swaggerRoute) { - Contract.Requires(oDataRoute != null); - Contract.Requires(oDataRoute.Constraints != null); + Contract.Requires(operation != null); + Contract.Requires(swaggerRoute != null); + Contract.Requires(swaggerRoute.ODataRoute.Constraints != null); Contract.Ensures(Contract.Result() != null); - var oDataPathRouteConstraint = oDataRoute.GetODataPathRouteConstraint(); + var oDataPathRouteConstraint = swaggerRoute.ODataRoute.GetODataPathRouteConstraint(); - var model = oDataRoute.GetEdmModel(); + var model = swaggerRoute.ODataRoute.GetEdmModel(); Contract.Assume(oDataPathRouteConstraint.PathHandler != null); - var result = oDataPathRouteConstraint.PathHandler.Parse(model, ServiceRoot.AppendPathSegment(oDataRoute.GetRoutePrefix()), sampleODataAbsoluteUri); + var odataPath = operation.GenerateSampleODataUri(ServiceRoot, swaggerRoute.Template).Replace(ServiceRoot, string.Empty); + + var result = oDataPathRouteConstraint.PathHandler.Parse(model, ServiceRoot, odataPath); Contract.Assume(result != null); return result; } diff --git a/Swashbuckle.OData/HttpConfigurationExtensions.cs b/Swashbuckle.OData/HttpConfigurationExtensions.cs index cde711f..05ab064 100644 --- a/Swashbuckle.OData/HttpConfigurationExtensions.cs +++ b/Swashbuckle.OData/HttpConfigurationExtensions.cs @@ -5,7 +5,6 @@ using System.Web.Http; using System.Web.Http.Routing; using System.Web.OData.Routing; -using Flurl; using Newtonsoft.Json; using Swashbuckle.OData.Descriptions; @@ -59,10 +58,10 @@ public static SwaggerRouteBuilder AddCustomSwaggerRoute(this HttpConfiguration h Contract.Requires(httpConfig.Properties != null); Contract.Ensures(Contract.Result() != null); - var fullRouteTemplate = HttpUtility.UrlDecode(oDataRoute.GetRoutePrefix().AppendPathSegment(routeTemplate)); - Contract.Assume(!string.IsNullOrWhiteSpace(fullRouteTemplate)); + var urlDecodedTemplate = HttpUtility.UrlDecode(routeTemplate); + Contract.Assume(!string.IsNullOrWhiteSpace(urlDecodedTemplate)); - var swaggerRoute = new SwaggerRoute(fullRouteTemplate, oDataRoute); + var swaggerRoute = new SwaggerRoute(urlDecodedTemplate, oDataRoute); var swaggerRouteBuilder = new SwaggerRouteBuilder(swaggerRoute); diff --git a/Swashbuckle.OData/Properties/AssemblyInfo.cs b/Swashbuckle.OData/Properties/AssemblyInfo.cs index 6f0aada..d329418 100644 --- a/Swashbuckle.OData/Properties/AssemblyInfo.cs +++ b/Swashbuckle.OData/Properties/AssemblyInfo.cs @@ -37,4 +37,4 @@ [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")] -[assembly: AssemblyInformationalVersion("2.13.0")] \ No newline at end of file +[assembly: AssemblyInformationalVersion("2.14.0")] \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml index 61a705d..f43e34c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 2.13.0.{build} +version: 2.14.0.{build} before_build: - cmd: nuget restore