diff --git a/Swashbuckle.OData.Nuget/Swashbuckle.OData.NuGet.nuproj b/Swashbuckle.OData.Nuget/Swashbuckle.OData.NuGet.nuproj
index 737392c..a465eae 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!
- Provides explicit, unit tested support for OData actions.
+ Provides explicit, unit tested support for OData actions. Fixes #59.
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.12.0
+ 2.12.1
diff --git a/Swashbuckle.OData.Sample/App_Start/FormatterConfig.cs b/Swashbuckle.OData.Sample/App_Start/FormatterConfig.cs
index 08a5f8f..c1d4673 100644
--- a/Swashbuckle.OData.Sample/App_Start/FormatterConfig.cs
+++ b/Swashbuckle.OData.Sample/App_Start/FormatterConfig.cs
@@ -1,6 +1,7 @@
using System.Web.Http;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
+using Newtonsoft.Json.Serialization;
namespace SwashbuckleODataSample
{
@@ -18,6 +19,7 @@ public static void Register(HttpConfiguration config)
formatters.JsonFormatter.SerializerSettings = new JsonSerializerSettings
{
+ ContractResolver = new CamelCasePropertyNamesContractResolver(),
NullValueHandling = NullValueHandling.Ignore,
DateFormatHandling = DateFormatHandling.IsoDateFormat,
DateTimeZoneHandling = DateTimeZoneHandling.Utc,
diff --git a/Swashbuckle.OData.Sample/App_Start/ODataConfig.cs b/Swashbuckle.OData.Sample/App_Start/ODataConfig.cs
index 51da9ff..06ab32e 100644
--- a/Swashbuckle.OData.Sample/App_Start/ODataConfig.cs
+++ b/Swashbuckle.OData.Sample/App_Start/ODataConfig.cs
@@ -63,36 +63,49 @@ private static void ConfigureWebApiOData(HttpConfiguration config)
private static IEdmModel GetDefaultModel()
{
var builder = new ODataConventionModelBuilder();
+ builder.EnableLowerCamelCase();
+
builder.EntitySet("Customers");
builder.EntitySet("Orders");
+
return builder.GetEdmModel();
}
private static IEdmModel GetCustomRouteModel()
{
var builder = new ODataConventionModelBuilder();
+ builder.EnableLowerCamelCase();
+
builder.EntitySet("Customers");
builder.EntitySet("Orders");
+
return builder.GetEdmModel();
}
private static IEdmModel GetVersionedModel()
{
var builder = new ODataConventionModelBuilder();
+ builder.EnableLowerCamelCase();
+
builder.EntitySet("Customers");
+
return builder.GetEdmModel();
}
private static IEdmModel GetFakeModel()
{
var builder = new ODataConventionModelBuilder();
+ builder.EnableLowerCamelCase();
+
builder.EntitySet("FakeCustomers");
+
return builder.GetEdmModel();
}
private static IEdmModel GetFunctionsEdmModel()
{
- ODataModelBuilder builder = new ODataConventionModelBuilder();
+ var builder = new ODataConventionModelBuilder();
+ builder.EnableLowerCamelCase();
builder.EntitySet("Products");
diff --git a/Swashbuckle.OData.Tests/Fixtures/ModelSchemaTests.cs b/Swashbuckle.OData.Tests/Fixtures/ModelSchemaTests.cs
new file mode 100644
index 0000000..e180774
--- /dev/null
+++ b/Swashbuckle.OData.Tests/Fixtures/ModelSchemaTests.cs
@@ -0,0 +1,89 @@
+using System;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Runtime.Serialization;
+using System.Threading.Tasks;
+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 ModelSchemaTests
+ {
+ [Test]
+ public async Task The_model_schema_matches_the_edm_model()
+ {
+ using (WebApp.Start(HttpClientUtils.BaseAddress, appBuilder => Configuration(appBuilder, typeof(BrandsController))))
+ {
+ // Arrange
+ var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress);
+ // Verify that the OData route in the test controller is valid
+ var result = await httpClient.GetAsync("/odata/Brands");
+ result.IsSuccessStatusCode.Should().BeTrue();
+
+ // Act
+ var swaggerDocument = await httpClient.GetJsonAsync("swagger/docs/v1");
+
+ // Assert
+ swaggerDocument.definitions.Should().ContainKey("Brand");
+ var brandSchema = swaggerDocument.definitions["Brand"];
+
+ brandSchema.properties.Should().ContainKey("id");
+ brandSchema.properties.Should().ContainKey("code");
+ brandSchema.properties.Should().ContainKey("name");
+ brandSchema.properties.Should().ContainKey("Something");
+
+ await ValidationUtils.ValidateSwaggerJson();
+ }
+ }
+
+ private static void Configuration(IAppBuilder appBuilder, Type targetController)
+ {
+ var config = appBuilder.GetStandardHttpConfig(targetController);
+
+ // Define a route to a controller class that contains functions
+ config.MapODataServiceRoute("ODataRoute", "odata", GetEdmModel());
+
+ config.EnsureInitialized();
+ }
+
+ private static IEdmModel GetEdmModel()
+ {
+ var builder = new ODataConventionModelBuilder();
+
+ builder.EntitySet("Brands");
+
+ builder.EnableLowerCamelCase(NameResolverOptions.ProcessReflectedPropertyNames | NameResolverOptions.ProcessExplicitPropertyNames);
+
+ return builder.GetEdmModel();
+ }
+ }
+
+ public class Brand
+ {
+ [Key]
+ public long Id { get; set; }
+ public string Code { get; set; }
+ public string Name { get; set; }
+
+ [DataMember(Name = "Something")]
+ public string Description { get; set; }
+ }
+
+ public class BrandsController : ODataController
+ {
+ [EnableQuery]
+ public IQueryable GetBrands()
+ {
+ return Enumerable.Empty().AsQueryable();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Swashbuckle.OData.Tests/Fixtures/SchemaTests.cs b/Swashbuckle.OData.Tests/Fixtures/SchemaTests.cs
index 669cd5d..808206a 100644
--- a/Swashbuckle.OData.Tests/Fixtures/SchemaTests.cs
+++ b/Swashbuckle.OData.Tests/Fixtures/SchemaTests.cs
@@ -49,7 +49,7 @@ public async Task Schema_contains_nested_reference_types_for_web_api_controllers
var swaggerDocument = await httpClient.GetJsonAsync("swagger/docs/v1");
// Assert
- swaggerDocument.definitions["Client"].properties.ContainsKey("Projects").Should().BeTrue();
+ swaggerDocument.definitions["Client"].properties.ContainsKey("projects").Should().BeTrue();
await ValidationUtils.ValidateSwaggerJson();
}
diff --git a/Swashbuckle.OData.Tests/Swashbuckle.OData.Tests.csproj b/Swashbuckle.OData.Tests/Swashbuckle.OData.Tests.csproj
index 3eae867..8a24c5b 100644
--- a/Swashbuckle.OData.Tests/Swashbuckle.OData.Tests.csproj
+++ b/Swashbuckle.OData.Tests/Swashbuckle.OData.Tests.csproj
@@ -120,6 +120,7 @@
True
+
..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll
True
@@ -143,6 +144,7 @@
+
diff --git a/Swashbuckle.OData/ODataSwaggerProvider.cs b/Swashbuckle.OData/ODataSwaggerProvider.cs
index e9670fc..edf0db0 100644
--- a/Swashbuckle.OData/ODataSwaggerProvider.cs
+++ b/Swashbuckle.OData/ODataSwaggerProvider.cs
@@ -2,9 +2,10 @@
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
-using System.ServiceModel.Description;
using System.Web.Http;
using System.Web.Http.Description;
+using System.Web.OData.Routing;
+using Microsoft.OData.Edm;
using Swashbuckle.Application;
using Swashbuckle.OData.Descriptions;
using Swashbuckle.Swagger;
@@ -90,7 +91,7 @@ public SwaggerDocument GetSwagger(string rootUrl, string apiVersion)
.Where(apiDesc => !(_options.IgnoreObsoleteActions && apiDesc.IsObsolete()))
.OrderBy(_options.GroupingKeySelector, _options.GroupingKeyComparer)
.GroupBy(apiDesc => apiDesc.RelativePathSansQueryString())
- .ToDictionary(group => "/" + group.Key, group => CreatePathItem(group, schemaRegistry));
+ .ToDictionary(group => "/" + group.Key, group => CreatePathItem(@group, schemaRegistry));
var rootUri = new Uri(rootUrl);
var port = !rootUri.IsDefaultPort ? ":" + rootUri.Port : string.Empty;
@@ -200,23 +201,25 @@ private Operation CreateOperation(ApiDescription apiDescription, SchemaRegistry
Contract.Requires(schemaRegistry != null);
Contract.Requires(apiDescription.ParameterDescriptions != null);
+
+ var edmModel = ((ODataRoute)apiDescription.Route).GetEdmModel();
+
var parameters = apiDescription.ParameterDescriptions
.Select(paramDesc =>
{
var inPath = apiDescription.RelativePathSansQueryString().Contains("{" + paramDesc.Name + "}");
var swaggerApiParameterDescription = paramDesc as SwaggerApiParameterDescription;
return swaggerApiParameterDescription != null
- ? CreateParameter(swaggerApiParameterDescription, inPath, schemaRegistry)
- : CreateParameter(paramDesc, inPath, schemaRegistry);
+ ? CreateParameter(swaggerApiParameterDescription, inPath, schemaRegistry, edmModel)
+ : CreateParameter(paramDesc, inPath, schemaRegistry, edmModel);
})
.ToList();
-
var responses = new Dictionary();
var responseType = apiDescription.ResponseType();
if (responseType == null || responseType == typeof(void))
responses.Add("204", new Response { description = "No Content" });
else
- responses.Add("200", new Response { description = "OK", schema = schemaRegistry.GetOrRegisterResponseType(responseType) });
+ responses.Add("200", new Response { description = "OK", schema = schemaRegistry.GetOrRegisterResponseType(edmModel, responseType) });
var operation = new Operation
{
@@ -239,7 +242,7 @@ private Operation CreateOperation(ApiDescription apiDescription, SchemaRegistry
return operation;
}
- private static Parameter CreateParameter(ApiParameterDescription paramDesc, bool inPath, SchemaRegistry schemaRegistry)
+ private static Parameter CreateParameter(ApiParameterDescription paramDesc, bool inPath, SchemaRegistry schemaRegistry, IEdmModel edmModel)
{
Contract.Requires(paramDesc != null);
Contract.Requires(schemaRegistry != null);
@@ -257,7 +260,7 @@ private static Parameter CreateParameter(ApiParameterDescription paramDesc, bool
@default = paramDesc.ParameterDescriptor.DefaultValue
};
- var schema = schemaRegistry.GetOrRegisterParameterType(paramDesc.ParameterDescriptor);
+ var schema = schemaRegistry.GetOrRegisterParameterType(edmModel, paramDesc.ParameterDescriptor);
if (parameter.@in == "body")
parameter.schema = schema;
else
@@ -266,7 +269,7 @@ private static Parameter CreateParameter(ApiParameterDescription paramDesc, bool
return parameter;
}
- private static Parameter CreateParameter(SwaggerApiParameterDescription paramDesc, bool inPath, SchemaRegistry schemaRegistry)
+ private static Parameter CreateParameter(SwaggerApiParameterDescription paramDesc, bool inPath, SchemaRegistry schemaRegistry, IEdmModel edmModel)
{
Contract.Requires(paramDesc != null);
Contract.Requires(schemaRegistry != null);
@@ -288,7 +291,7 @@ private static Parameter CreateParameter(SwaggerApiParameterDescription paramDes
var parameterType = paramDesc.ParameterDescriptor.ParameterType;
Contract.Assume(parameterType != null);
- var schema = schemaRegistry.GetOrRegisterParameterType(paramDesc.ParameterDescriptor);
+ var schema = schemaRegistry.GetOrRegisterParameterType(edmModel, paramDesc.ParameterDescriptor);
if (parameter.@in == "body")
parameter.schema = schema;
else
diff --git a/Swashbuckle.OData/Properties/AssemblyInfo.cs b/Swashbuckle.OData/Properties/AssemblyInfo.cs
index 3400bc4..f6ef64c 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.12.0")]
\ No newline at end of file
+[assembly: AssemblyInformationalVersion("2.12.1")]
\ No newline at end of file
diff --git a/Swashbuckle.OData/SchemaRegistryExtensions.cs b/Swashbuckle.OData/SchemaRegistryExtensions.cs
index 24791f1..39f52e2 100644
--- a/Swashbuckle.OData/SchemaRegistryExtensions.cs
+++ b/Swashbuckle.OData/SchemaRegistryExtensions.cs
@@ -1,10 +1,15 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.Serialization;
using System.ServiceModel.Description;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.OData;
+using System.Web.OData.Formatter;
+using Microsoft.OData.Edm;
using Swashbuckle.OData.Descriptions;
using Swashbuckle.Swagger;
@@ -12,7 +17,7 @@ namespace Swashbuckle.OData
{
internal static class SchemaRegistryExtensions
{
- public static Schema GetOrRegisterParameterType(this SchemaRegistry registry, HttpParameterDescriptor parameterDescriptor)
+ public static Schema GetOrRegisterParameterType(this SchemaRegistry registry, IEdmModel edmModel, HttpParameterDescriptor parameterDescriptor)
{
if (IsODataActionParameter(parameterDescriptor))
{
@@ -20,11 +25,20 @@ public static Schema GetOrRegisterParameterType(this SchemaRegistry registry, Ht
}
if (IsAGenericODataTypeThatShouldBeUnwrapped(parameterDescriptor.ParameterType, MessageDirection.Input))
{
- var genericArguments = parameterDescriptor.ParameterType.GetGenericArguments();
- Contract.Assume(genericArguments != null);
- return registry.GetOrRegister(genericArguments[0]);
+ return HandleGenericODataTypeThatShouldBeUnwrapped(registry, edmModel, parameterDescriptor.ParameterType);
}
- return registry.GetOrRegister(parameterDescriptor.ParameterType);
+ var schema1 = registry.GetOrRegister(parameterDescriptor.ParameterType);
+ ApplyEdmModelPropertyNamesToSchema(registry, edmModel, parameterDescriptor.ParameterType);
+ return schema1;
+ }
+
+ private static Schema HandleGenericODataTypeThatShouldBeUnwrapped(SchemaRegistry registry, IEdmModel edmModel, Type type)
+ {
+ var genericArguments = type.GetGenericArguments();
+ Contract.Assume(genericArguments != null);
+ var schema = registry.GetOrRegister(genericArguments[0]);
+ ApplyEdmModelPropertyNamesToSchema(registry, edmModel, genericArguments[0]);
+ return schema;
}
private static bool IsODataActionParameter(HttpParameterDescriptor parameterDescriptor)
@@ -32,16 +46,14 @@ private static bool IsODataActionParameter(HttpParameterDescriptor parameterDesc
return parameterDescriptor is ODataActionParameterDescriptor;
}
- public static Schema GetOrRegisterResponseType(this SchemaRegistry registry, Type type)
+ public static Schema GetOrRegisterResponseType(this SchemaRegistry registry, IEdmModel edmModel, Type type)
{
Contract.Requires(registry != null);
Contract.Requires(type != null);
if (IsAGenericODataTypeThatShouldBeUnwrapped(type, MessageDirection.Output))
{
- var genericArguments = type.GetGenericArguments();
- Contract.Assume(genericArguments != null);
- return registry.GetOrRegister(genericArguments[0]);
+ return HandleGenericODataTypeThatShouldBeUnwrapped(registry, edmModel, type);
}
Type elementType;
if (IsResponseCollection(type, MessageDirection.Output, out elementType))
@@ -50,15 +62,55 @@ public static Schema GetOrRegisterResponseType(this SchemaRegistry registry, Typ
var listType = openListType.MakeGenericType(elementType);
var openOdataType = typeof (ODataResponse<>);
var odataType = openOdataType.MakeGenericType(listType);
- return registry.GetOrRegister(odataType);
+ var schema = registry.GetOrRegister(odataType);
+ ApplyEdmModelPropertyNamesToSchema(registry, edmModel, elementType);
+ return schema;
}
if (IsResponseWithPrimiveTypeNotSupportedByJson(type, MessageDirection.Output))
{
var openOdataType = typeof(ODataResponse<>);
var odataType = openOdataType.MakeGenericType(type);
- return registry.GetOrRegister(odataType);
+ var schema = registry.GetOrRegister(odataType);
+ return schema;
+ }
+ var schema1 = registry.GetOrRegister(type);
+ ApplyEdmModelPropertyNamesToSchema(registry, edmModel, type);
+ return schema1;
+ }
+
+ private static void ApplyEdmModelPropertyNamesToSchema(SchemaRegistry registry, IEdmModel edmModel, Type type)
+ {
+ var entityReference = registry.GetOrRegister(type);
+ if (entityReference.@ref != null)
+ {
+ var definitionKey = entityReference.@ref.Replace("#/definitions/", string.Empty);
+ var schemaDefinition = registry.Definitions[definitionKey];
+ var edmType = edmModel.GetEdmType(type) as IEdmStructuredType;
+ if (edmType != null)
+ {
+ schemaDefinition.properties = schemaDefinition.properties.ToDictionary(property =>
+ {
+ var currentProperty = type.GetProperty(property.Key, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
+ return GetEdmPropertyName(currentProperty, edmType);
+ }, property => property.Value);
+ }
}
- return registry.GetOrRegister(type);
+ }
+
+ private static string GetEdmPropertyName(MemberInfo currentProperty, IEdmStructuredType edmType)
+ {
+ var currentPropertyName = GetPropertyNameForEdmModel(currentProperty);
+
+ var edmProperty = edmType.Properties().SingleOrDefault(property => property.Name.Equals(currentPropertyName, StringComparison.CurrentCultureIgnoreCase));
+
+ return edmProperty != null ? edmProperty.Name : currentPropertyName;
+ }
+
+ private static string GetPropertyNameForEdmModel(MemberInfo currentProperty)
+ {
+ var dataMemberAttribute = currentProperty.GetCustomAttributes()?.SingleOrDefault();
+
+ return !string.IsNullOrWhiteSpace(dataMemberAttribute?.Name) ? dataMemberAttribute.Name : currentProperty.Name;
}
private static bool IsResponseWithPrimiveTypeNotSupportedByJson(Type type, MessageDirection messageDirection)
diff --git a/Swashbuckle.OData/Swashbuckle.OData.csproj b/Swashbuckle.OData/Swashbuckle.OData.csproj
index ea2344b..50e5f7c 100644
--- a/Swashbuckle.OData/Swashbuckle.OData.csproj
+++ b/Swashbuckle.OData/Swashbuckle.OData.csproj
@@ -154,6 +154,7 @@
True
+
diff --git a/appveyor.yml b/appveyor.yml
index 6f858e6..aefb039 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,4 +1,4 @@
-version: 2.12.0.{build}
+version: 2.12.1.{build}
before_build:
- cmd: nuget restore