Skip to content

Commit

Permalink
Merge pull request #27 from rbeauchamp/feature/22
Browse files Browse the repository at this point in the history
#22: fix issues with OData functions
  • Loading branch information
rbeauchamp committed Dec 20, 2015
2 parents 69c4bc7 + acad929 commit bc9030d
Show file tree
Hide file tree
Showing 24 changed files with 563 additions and 96 deletions.
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>Supports custom OData routes, Refactored to support DI per http://blog.ploeh.dk/2014/05/19/di-friendly-library/</ReleaseNotes>
<ReleaseNotes>Fixes issues with OData functions</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.6.0</Version>
<Version>2.6.1</Version>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Swashbuckle.OData\Swashbuckle.OData.csproj" />
Expand Down
72 changes: 63 additions & 9 deletions Swashbuckle.OData.Sample/App_Start/ODataConfig.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http;
using System.Web.Http;
using System.Web.Http.Description;
using System.Web.Http.Dispatcher;
using System.Web.OData.Builder;
using System.Web.OData.Extensions;
Expand All @@ -12,7 +10,6 @@
using Microsoft.Restier.WebApi;
using Microsoft.Restier.WebApi.Batch;
using Swashbuckle.OData;
using Swashbuckle.OData.Descriptions;
using SwashbuckleODataSample.Models;
using SwashbuckleODataSample.Repositories;
using SwashbuckleODataSample.Versioning;
Expand All @@ -37,7 +34,7 @@ private static async void ConfigureRestierOData(HttpConfiguration config)
private static void ConfigureWebApiOData(HttpConfiguration config)
{
var controllerSelector = new ODataVersionControllerSelector(config);
config.Services.Replace(typeof (IHttpControllerSelector), controllerSelector);
config.Services.Replace(typeof(IHttpControllerSelector), controllerSelector);

// Define a versioned route
config.MapODataServiceRoute("V1RouteVersioning", "odata/v1", GetVersionedModel());
Expand All @@ -50,23 +47,35 @@ private static void ConfigureWebApiOData(HttpConfiguration config)
// Define a custom route with custom routing conventions
var conventions = ODataRoutingConventions.CreateDefault();
conventions.Insert(0, new CustomNavigationPropertyRoutingConvention());
var customODataRoute = config.MapODataServiceRoute("CustomODataRoute", ODataRoutePrefix, GetModel(), batchHandler: null, pathHandler: new DefaultODataPathHandler(), routingConventions: conventions);
var customODataRoute = config.MapODataServiceRoute("CustomODataRoute", ODataRoutePrefix, GetCustomRouteModel(), batchHandler: null, pathHandler: new DefaultODataPathHandler(), routingConventions: conventions);
config.AddCustomSwaggerRoute(customODataRoute, "/Customers({Id})/Orders")
.Operation(HttpMethod.Post)
.PathParameter<int>("Id")
.BodyParameter<Order>("order");

// Define a default non-versioned route (default route should be at the end as a last catch-all)
config.MapODataServiceRoute("DefaultODataRoute", ODataRoutePrefix, GetModel());
// 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());
}

private static IEdmModel GetModel()
private static IEdmModel GetDefaultModel()
{
var builder = new ODataConventionModelBuilder();
builder.EntitySet<Customer>("Customers");
builder.EntitySet<Order>("Orders");
return builder.GetEdmModel();
}

private static IEdmModel GetCustomRouteModel()
{
var builder = new ODataConventionModelBuilder();
builder.EntitySet<Customer>("Customers");
builder.EntitySet<Order>("Orders");
return builder.GetEdmModel();
}

private static IEdmModel GetVersionedModel()
{
var builder = new ODataConventionModelBuilder();
Expand All @@ -80,5 +89,50 @@ private static IEdmModel GetFakeModel()
builder.EntitySet<Customer>("FakeCustomers");
return builder.GetEdmModel();
}

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

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

var productType = builder.EntityType<Product>();

// Function bound to a collection
// Returns the most expensive product, a single entity
productType.Collection
.Function("MostExpensive")
.Returns<double>();

// Function bound to a collection
// Returns the top 10 product, a collection
productType.Collection
.Function("Top10")
.ReturnsCollectionFromEntitySet<Product>("Products");

// Function bound to a single entity
// Returns the instance's price rank among all products
productType
.Function("GetPriceRank")
.Returns<int>();

// Function bound to a single entity
// Accept a string as parameter and return a double
// This function calculate the general sales tax base on the
// state
productType
.Function("CalculateGeneralSalesTax")
.Returns<double>()
.Parameter<string>("state");

// Commented out because unbound functions don't
// play well in a config with mulitple OData routes
// Unbound Function
//builder.Function("GetSalesTaxRate")
// .Returns<double>()
// .Parameter<string>("state");

return builder.GetEdmModel();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ public void Apply(SwaggerDocument swaggerDoc, SchemaRegistry schemaRegistry, IAp
new Tag { name = "Customers", description = "an ODataController resource" },
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 = "Users", description = "a RESTier resource" },
new Tag { name = "Products", description = "demonstrates functions" }
};
}
}
Expand Down
2 changes: 2 additions & 0 deletions Swashbuckle.OData.Sample/Models/Order.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ public class Order

public string OrderName { get; set; }

public double UnitPrice { get; set; }

public int CustomerId { get; set; }

[ForeignKey("CustomerId")]
Expand Down
14 changes: 14 additions & 0 deletions Swashbuckle.OData.Sample/Models/Product.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.ComponentModel.DataAnnotations;

namespace SwashbuckleODataSample.Models
{
public class Product
{
[Key]
public int Id { get; set; }

public string Name { get; set; }

public double Price { get; set; }
}
}
12 changes: 12 additions & 0 deletions Swashbuckle.OData.Sample/ODataControllers/CustomersController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Web.Http;
using System.Web.Http.Description;
using System.Web.OData;
using System.Web.OData.Routing;
using SwashbuckleODataSample.Models;
using SwashbuckleODataSample.Repositories;

Expand Down Expand Up @@ -156,6 +157,17 @@ public IQueryable<Order> GetOrders([FromODataUri] int key)
.SelectMany(m => m.Orders);
}

/// <summary>
/// An OData function example. Gets the total count of customers.
/// </summary>
[HttpGet]
[ResponseType(typeof(int))]
public IHttpActionResult TotalCount()
{
var customerCount = _db.Customers.Count();
return Ok(customerCount);
}

protected override void Dispose(bool disposing)
{
if (disposing)
Expand Down
179 changes: 179 additions & 0 deletions Swashbuckle.OData.Sample/ODataControllers/ProductsController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
using System.Web.Http.Description;
using System.Web.OData;
using System.Web.OData.Routing;
using SwashbuckleODataSample.Models;

namespace SwashbuckleODataSample.ODataControllers
{
public class ProductsController : ODataController
{
private static readonly ConcurrentDictionary<int, Product> Data;

static ProductsController()
{
Data = new ConcurrentDictionary<int, Product>();
var rand = new Random();

Enumerable.Range(0, 100).Select(i => new Product
{
Id = i,
Name = "Product " + i,
Price = rand.NextDouble()*1000
}).ToList().ForEach(p => Data.TryAdd(p.Id, p));
}

/// <summary>
/// Get the most expensive product. This is a function bound to a collection.
/// </summary>
[HttpGet]
[ResponseType(typeof(double))]
public IHttpActionResult MostExpensive()
{
var retval = Data.Max(pair => pair.Value.Price);

return Ok(retval);
}

/// <summary>
/// Get the top 10 most expensive products. This is a function bound to a collection that returns a collection.
/// </summary>
[HttpGet]
[ResponseType(typeof(List<Product>))]
public IHttpActionResult Top10()
{
var retval = Data.Values.OrderByDescending(p => p.Price).Take(10).ToList();

return Ok(retval);
}

/// <summary>
/// Get the rank of the product price. This is a function bound to an entity.
/// </summary>
/// <param name="key">The product id</param>
[HttpGet]
[ResponseType(typeof(int))]
public IHttpActionResult GetPriceRank(int key)
{
Product p;
if (Data.TryGetValue(key, out p))
{
// NOTE: Use where clause to get the rank of the price may not
// offer the good time complexity. The following code is intended
// for demostration only.
return Ok(Data.Values.Count(one => one.Price > p.Price));
}
return NotFound();
}

/// <summary>
/// Get the sales tax for a product in a given state. This is a function which accepts a parameter.
/// </summary>
/// <param name="key">The product id</param>
/// <param name="state">The state</param>
[HttpGet]
[ResponseType(typeof(double))]
public IHttpActionResult CalculateGeneralSalesTax(int key, string state)
{
var taxRate = GetRate(state);

Product product;
if (Data.TryGetValue(key, out product))
{
var tax = product.Price*taxRate/100;
return Ok(tax);
}
return NotFound();
}

///// <summary>
///// Get the sales tax rate for a state. This is an unbound function.
///// </summary>
///// <param name="state">The state</param>
//[HttpGet]
//[ResponseType(typeof(double))]
//[ODataRoute("GetSalesTaxRate(state={state})")]
//public IHttpActionResult GetSalesTaxRate([FromODataUri] string state)
//{
// return Ok(GetRate(state));
//}

private static double GetRate(string state)
{
double taxRate;
switch (state)
{
case "AZ":
taxRate = 5.6;
break;
case "CA":
taxRate = 7.5;
break;
case "CT":
taxRate = 6.35;
break;
case "GA":
taxRate = 4;
break;
case "IN":
taxRate = 7;
break;
case "KS":
taxRate = 6.15;
break;
case "KY":
taxRate = 6;
break;
case "MA":
taxRate = 6.25;
break;
case "NV":
taxRate = 6.85;
break;
case "NJ":
taxRate = 7;
break;
case "NY":
taxRate = 4;
break;
case "NC":
taxRate = 4.75;
break;
case "ND":
taxRate = 5;
break;
case "PA":
taxRate = 6;
break;
case "TN":
taxRate = 7;
break;
case "TX":
taxRate = 6.25;
break;
case "VA":
taxRate = 4.3;
break;
case "WA":
taxRate = 6.5;
break;
case "WV":
taxRate = 6;
break;
case "WI":
taxRate = 5;
break;

default:
taxRate = 0;
break;
}

return taxRate;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ protected override void Seed(SwashbuckleODataContext context)
context.Customers.Add(customerOne);
context.Customers.Add(new Customer { Name = "CustomerTwo" });

context.Orders.Add(new Order { OrderId = SequentialGuidGenerator.Generate(SequentialGuidType.SequentialAtEnd), OrderName = "OrderOne", Customer = customerOne });
context.Orders.Add(new Order { OrderId = SequentialGuidGenerator.Generate(SequentialGuidType.SequentialAtEnd), OrderName = "OrderTwo", Customer = customerOne });
context.Orders.Add(new Order { OrderId = SequentialGuidGenerator.Generate(SequentialGuidType.SequentialAtEnd), OrderName = "OrderOne", Customer = customerOne, UnitPrice = 4.0 });
context.Orders.Add(new Order { OrderId = SequentialGuidGenerator.Generate(SequentialGuidType.SequentialAtEnd), OrderName = "OrderTwo", Customer = customerOne, UnitPrice = 3.5 });

base.Seed(context);
}
Expand Down
Loading

0 comments on commit bc9030d

Please sign in to comment.