Skip to content

Commit

Permalink
Merge branch 'master'
Browse files Browse the repository at this point in the history
  • Loading branch information
Richard Beauchamp committed Feb 11, 2016
2 parents 250ecc5 + c2e3f99 commit 9011093
Show file tree
Hide file tree
Showing 63 changed files with 1,918 additions and 368 deletions.
7 changes: 7 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
; Top-most EditorConfig file
root = true

; 4-column space indention
[*.cs]
indent_style = space
indent_size = 4
28 changes: 27 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Swashbuckle.OData
=========

[![Build status](https://ci.appveyor.com/api/projects/status/lppv9403dgwrntpa?svg=true)](https://ci.appveyor.com/project/rbeauchamp/swashbuckle-odata/)
[![Coverage Status](https://coveralls.io/repos/rbeauchamp/Swashbuckle.OData/badge.svg?branch=master&service=github)](https://coveralls.io/github/rbeauchamp/Swashbuckle.OData?branch=master)
[![Coverage Status](https://coveralls.io/repos/github/rbeauchamp/Swashbuckle.OData/badge.svg?branch=master)](https://coveralls.io/github/rbeauchamp/Swashbuckle.OData?branch=master)
[![Issue Stats](http://www.issuestats.com/github/rbeauchamp/Swashbuckle.OData/badge/pr)](http://www.issuestats.com/github/rbeauchamp/Swashbuckle.OData)
[![Issue Stats](http://www.issuestats.com/github/rbeauchamp/Swashbuckle.OData/badge/issue)](http://www.issuestats.com/github/rbeauchamp/Swashbuckle.OData)

Expand Down Expand Up @@ -61,6 +61,32 @@ config.AddCustomSwaggerRoute(restierRoute, "/Customers({CustomerId})/Orders({Ord
.PathParameter<int>("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:
Expand Down
4 changes: 2 additions & 2 deletions Swashbuckle.OData.Nuget/Swashbuckle.OData.NuGet.nuproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@
<Owners>Richard Beauchamp</Owners>
<Summary>Extends Swashbuckle with OData v4 support!</Summary>
<Description>Extends Swashbuckle with OData v4 support!</Description>
<ReleaseNotes>Provides explicit, unit tested support for OData actions.</ReleaseNotes>
<ReleaseNotes>OData query parameters are limited to $expand and $select for APIs that return a single result.</ReleaseNotes>
<ProjectUrl>https://github.com/rbeauchamp/Swashbuckle.OData</ProjectUrl>
<LicenseUrl>https://github.com/rbeauchamp/Swashbuckle.OData/blob/master/License.txt</LicenseUrl>
<Copyright>Copyright 2015</Copyright>
<Tags>Swashbuckle Swagger SwaggerUi OData Documentation Discovery Help WebApi AspNet AspNetWebApi Docs WebHost IIS</Tags>
<Version>2.12.0</Version>
<Version>2.15.0</Version>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Swashbuckle.OData\Swashbuckle.OData.csproj" />
Expand Down
2 changes: 2 additions & 0 deletions Swashbuckle.OData.Sample/App_Start/FormatterConfig.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Web.Http;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;

namespace SwashbuckleODataSample
{
Expand All @@ -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,
Expand Down
31 changes: 25 additions & 6 deletions Swashbuckle.OData.Sample/App_Start/ODataConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,36 +63,49 @@ private static void ConfigureWebApiOData(HttpConfiguration config)
private static IEdmModel GetDefaultModel()
{
var builder = new ODataConventionModelBuilder();
builder.EnableLowerCamelCase();

builder.EntitySet<Customer>("Customers");
builder.EntitySet<Order>("Orders");

return builder.GetEdmModel();
}

private static IEdmModel GetCustomRouteModel()
{
var builder = new ODataConventionModelBuilder();
builder.EnableLowerCamelCase();

builder.EntitySet<Customer>("Customers");
builder.EntitySet<Order>("Orders");

return builder.GetEdmModel();
}

private static IEdmModel GetVersionedModel()
{
var builder = new ODataConventionModelBuilder();
builder.EnableLowerCamelCase();

builder.EntitySet<Customer>("Customers");

return builder.GetEdmModel();
}

private static IEdmModel GetFakeModel()
{
var builder = new ODataConventionModelBuilder();
builder.EnableLowerCamelCase();

builder.EntitySet<Customer>("FakeCustomers");

return builder.GetEdmModel();
}

private static IEdmModel GetFunctionsEdmModel()
{
ODataModelBuilder builder = new ODataConventionModelBuilder();
var builder = new ODataConventionModelBuilder();
builder.EnableLowerCamelCase();

builder.EntitySet<Product>("Products");

Expand Down Expand Up @@ -139,11 +152,17 @@ private static IEdmModel GetFunctionsEdmModel()

// An action bound to an entity set
// Accepts multiple action parameters
var action = productType.Collection.Action("Create");
action.ReturnsFromEntitySet<Product>("Products");
action.Parameter<string>("Name").OptionalParameter = false;
action.Parameter<double>("Price").OptionalParameter = false;
action.Parameter<MyEnum>("EnumValue").OptionalParameter = false;
var createAction = productType.Collection.Action("Create");
createAction.ReturnsFromEntitySet<Product>("Products");
createAction.Parameter<string>("Name").OptionalParameter = false;
createAction.Parameter<double>("Price").OptionalParameter = false;
createAction.Parameter<MyEnum>("EnumValue").OptionalParameter = false;

// An action bound to an entity set
// Accepts an array of complex types
var postArrayAction = productType.Collection.Action("PostArray");
postArrayAction.ReturnsFromEntitySet<Product>("Products");
postArrayAction.CollectionParameter<ProductDto>("products").OptionalParameter = false;

// An action bound to an entity
productType.Action("Rate")
Expand Down
3 changes: 1 addition & 2 deletions Swashbuckle.OData.Sample/App_Start/WebApiConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();

config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}", new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}", new { id = RouteParameter.Optional });
}
}
}
9 changes: 9 additions & 0 deletions Swashbuckle.OData.Sample/Models/Product.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,13 @@ public class Product

public MyEnum EnumValue { get; set; }
}

public class ProductDto
{
public string Name { get; set; }

public double Price { get; set; }

public MyEnum EnumValue { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
Expand Down
32 changes: 31 additions & 1 deletion Swashbuckle.OData.Sample/ODataControllers/ProductsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ public IQueryable<Product> ProductsWithIds([FromODataUri]int[] Ids)
}

/// <summary>
/// Creates a product. This action accepts parameters via an ODataActionParameters object.
/// Creates a product from values passed in an ODataActionParameters object.
/// </summary>
/// <param name="parameters">The OData action parameters.</param>
[HttpPost]
Expand All @@ -143,6 +143,36 @@ public IHttpActionResult Create(ODataActionParameters parameters)
return Created(product);
}

/// <summary>
/// Creates a set of products from an array of ProductDTOs passed in an ODataActionParameters object.
/// </summary>
/// <param name="parameters">The OData action parameters containing an array of ProductDTOs</param>
[HttpPost]
public List<Product> PostArray(ODataActionParameters parameters)
{
var productDtos = parameters["products"] as IEnumerable<ProductDto>;

var newProducts = new List<Product>();

if (productDtos != null && productDtos.Any())
{
foreach (var productDto in productDtos)
{
var product = new Product
{
Id = Data.Values.Max(existingProduct => existingProduct.Id) + 1,
Name = productDto.Name,
Price = productDto.Price,
EnumValue = productDto.EnumValue
};
newProducts.Add(product);
Data.TryAdd(product.Id, product);
}
}

return newProducts;
}

/// <summary>
/// Rates a product. This action targets a specific entity by id.
/// </summary>
Expand Down
15 changes: 12 additions & 3 deletions Swashbuckle.OData.Tests/AppBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,22 @@ namespace Swashbuckle.OData.Tests
{
public static class AppBuilderExtensions
{
public static HttpConfiguration GetStandardHttpConfig(this IAppBuilder appBuilder, Type targetController, Action<SwaggerDocsConfig> unitTestConfigs = null)
public static HttpConfiguration GetStandardHttpConfig(this IAppBuilder appBuilder, params Type[] targetControllers)
{
return GetStandardHttpConfig(appBuilder, null, targetControllers);
}

public static HttpConfiguration GetStandardHttpConfig(this IAppBuilder appBuilder, Action<SwaggerDocsConfig> unitTestConfigs, params Type[] targetControllers)
{
var config = new HttpConfiguration
{
IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always
};
return ConfigureHttpConfig(appBuilder, config, unitTestConfigs, targetControllers);
}

public static HttpConfiguration ConfigureHttpConfig(this IAppBuilder appBuilder, HttpConfiguration config, Action<SwaggerDocsConfig> unitTestConfigs, params Type[] targetControllers)
{
var server = new HttpServer(config);
appBuilder.UseWebApi(server);
config.EnableSwagger(c =>
Expand All @@ -32,12 +42,11 @@ public static HttpConfiguration GetStandardHttpConfig(this IAppBuilder appBuilde
// Apply test-specific configs
unitTestConfigs?.Invoke(c);
}).EnableSwaggerUi();

FormatterConfig.Register(config);

config.Services.Replace(typeof (IHttpControllerSelector), new UnitTestControllerSelector(config, targetController));
config.Services.Replace(typeof (IHttpControllerSelector), new UnitTestControllerSelector(config, targetControllers));

return config;
}
Expand Down
80 changes: 80 additions & 0 deletions Swashbuckle.OData.Tests/Fixtures/ActionTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Net;
Expand All @@ -12,6 +13,7 @@
using FluentAssertions;
using Microsoft.OData.Edm;
using Microsoft.Owin.Hosting;
using Newtonsoft.Json;
using NUnit.Framework;
using Owin;
using Swashbuckle.Swagger;
Expand All @@ -22,6 +24,62 @@ namespace Swashbuckle.OData.Tests
[TestFixture]
public class ActionTests
{
[Test]
public async Task It_supports_actions_that_accept_an_array_of_complex_types()
{
using (WebApp.Start(HttpClientUtils.BaseAddress, appBuilder => Configuration(appBuilder, typeof(SuppliersController))))
{
// Arrange
var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress);
// Verify that the OData route and post data to the test controller is valid
var suppliers = new SupplierDtos
{
Suppliers = new List<SupplierDto>
{
new SupplierDto
{
Name = "SupplierNameOne",
Code = "CodeOne",
Description = "SupplierOneDescription"
},
new SupplierDto
{
Name = "SupplierNameTwo",
Code = "CodeTwo",
Description = "SupplierTwoDescription"
}
}
};

var result = await httpClient.PostAsJsonAsync("/odata/Suppliers/Default.PostArrayOfSuppliers", suppliers);
result.IsSuccessStatusCode.Should().BeTrue();

// Act
var swaggerDocument = await httpClient.GetJsonAsync<SwaggerDocument>("swagger/docs/v1");

// Assert
PathItem pathItem;
swaggerDocument.paths.TryGetValue("/odata/Suppliers/Default.PostArrayOfSuppliers", out pathItem);
pathItem.Should().NotBeNull();
pathItem.post.Should().NotBeNull();
pathItem.post.parameters.Count.Should().Be(1);
pathItem.post.parameters.Single().@in.Should().Be("body");
pathItem.post.parameters.Single().name.Should().Be("parameters");
pathItem.post.parameters.Single().schema.Should().NotBeNull();
pathItem.post.parameters.Single().schema.type.Should().Be("object");
pathItem.post.parameters.Single().schema.properties.Should().NotBeNull();
pathItem.post.parameters.Single().schema.properties.Count.Should().Be(1);
pathItem.post.parameters.Single().schema.properties.Should().ContainKey("suppliers");
pathItem.post.parameters.Single().schema.properties.Single(pair => pair.Key == "suppliers").Value.type.Should().Be("array");
pathItem.post.parameters.Single().schema.properties.Single(pair => pair.Key == "suppliers").Value.items.Should().NotBeNull();
pathItem.post.parameters.Single().schema.properties.Single(pair => pair.Key == "suppliers").Value.items.@ref.Should().Be("#/definitions/SupplierDto");

swaggerDocument.definitions.Keys.Should().Contain("SupplierDto");

await ValidationUtils.ValidateSwaggerJson();
}
}

[Test]
public async Task It_supports_actions_with_only_body_paramters()
{
Expand Down Expand Up @@ -170,6 +228,7 @@ private static IEdmModel GetEdmModel()
{
var builder = new ODataConventionModelBuilder();
builder.EntitySet<Supplier>("Suppliers");
//builder.ComplexType<SupplierDto>();
var entityType = builder.EntityType<Supplier>();

var create = entityType.Collection.Action("Create");
Expand All @@ -182,13 +241,23 @@ private static IEdmModel GetEdmModel()
createWithEnum.ReturnsFromEntitySet<Supplier>("Suppliers");
createWithEnum.Parameter<MyEnum?>("EnumValue");

var postArray = entityType.Collection.Action("PostArrayOfSuppliers");
postArray.ReturnsCollectionFromEntitySet<Supplier>("Suppliers");
postArray.CollectionParameter<SupplierDto>("suppliers");

entityType.Action("Rate")
.Parameter<int>("Rating");

return builder.GetEdmModel();
}
}

public class SupplierDtos
{
[JsonProperty("suppliers")]
public List<SupplierDto> Suppliers { get; set; }
}

public class SupplierDto
{
public string Code { get; set; }
Expand Down Expand Up @@ -217,6 +286,17 @@ public class Supplier

public class SuppliersController : ODataController
{
[HttpPost]
[ResponseType(typeof(List<Supplier>))]
public List<Supplier> PostArrayOfSuppliers(ODataActionParameters parameters)
{
parameters.Should().ContainKey("suppliers");
parameters.Count.Should().Be(1);
parameters["suppliers"].Should().BeAssignableTo<IEnumerable<SupplierDto>>();

return new List<Supplier>();
}

[HttpPost]
[ResponseType(typeof(Supplier))]
public IHttpActionResult Create(ODataActionParameters parameters)
Expand Down
Loading

0 comments on commit 9011093

Please sign in to comment.