Skip to content

Commit

Permalink
Merge pull request #30 from rbeauchamp/feature/24
Browse files Browse the repository at this point in the history
Provide support for OData containment and attribute routing
  • Loading branch information
Richard Beauchamp committed Dec 24, 2015
2 parents bc9030d + 96be7cc commit 690c836
Show file tree
Hide file tree
Showing 57 changed files with 2,240 additions and 770 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>Fixes issues with OData functions</ReleaseNotes>
<ReleaseNotes>Provides support for containment and ODataRoute attributes.</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.1</Version>
<Version>2.7.0</Version>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Swashbuckle.OData\Swashbuckle.OData.csproj" />
Expand Down
45 changes: 45 additions & 0 deletions Swashbuckle.OData.Tests/AppBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System;
using System.Web.Http;
using System.Web.Http.Dispatcher;
using Owin;
using Swashbuckle.Application;
using SwashbuckleODataSample;

namespace Swashbuckle.OData.Tests
{
public static class AppBuilderExtensions
{
public static HttpConfiguration GetStandardHttpConfig(this IAppBuilder appBuilder, Type targetController, Action<SwaggerDocsConfig> unitTestConfigs = null)
{
var config = new HttpConfiguration
{
IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always
};
var server = new HttpServer(config);
appBuilder.UseWebApi(server);
config.EnableSwagger(c =>
{
// Use "SingleApiVersion" to describe a single version API. Swagger 2.0 includes an "Info" object to
// hold additional metadata for an API. Version and title are required but you can also provide
// additional fields by chaining methods off SingleApiVersion.
//
c.SingleApiVersion("v1", "A title for your API");
// Wrap the default SwaggerGenerator with additional behavior (e.g. caching) or provide an
// alternative implementation for ISwaggerProvider with the CustomProvider option.
//
c.CustomProvider(defaultProvider => new ODataSwaggerProvider(defaultProvider, c, config));
// Apply test-specific configs
unitTestConfigs?.Invoke(c);
}).EnableSwaggerUi();

FormatterConfig.Register(config);

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

return config;
}
}
}
161 changes: 161 additions & 0 deletions Swashbuckle.OData.Tests/Containment/AccountsController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Web.Http;
using System.Web.OData;
using System.Web.OData.Extensions;
using System.Web.OData.Routing;
using Containment;

namespace Swashbuckle.OData.Tests.Containment
{
public class AccountsController : ODataController
{
private static IList<Account> _accounts;

public AccountsController()
{
if (_accounts == null)
{
_accounts = InitAccounts();
}
}

[EnableQuery]
public IHttpActionResult Get()
{
return Ok(_accounts.AsQueryable());
}

[EnableQuery]
public IHttpActionResult GetPayinPIs(int key)
{
var payinPIs = _accounts.Single(a => a.AccountID == key).PayinPIs;
return Ok(payinPIs);
}

[EnableQuery]
[ODataRoute("Accounts({accountId})/PayinPIs({paymentInstrumentId})")]
public IHttpActionResult GetSinglePayinPI(int accountId, int paymentInstrumentId)
{
var payinPIs = _accounts.Single(a => a.AccountID == accountId).PayinPIs;
var payinPI = payinPIs.Single(pi => pi.PaymentInstrumentID == paymentInstrumentId);
return Ok(payinPI);
}

[EnableQuery]
public IHttpActionResult GetPayoutPI(int key, int piKey)
{
var payoutPI = _accounts.Single(a => a.AccountID == key).PayoutPI;
return Ok(payoutPI);
}

// POST ~/Accounts(100)/PayinPIs
public IHttpActionResult PostToPayinPIsFromAccount(int key, PaymentInstrument pi)
{
var account = _accounts.Single(a => a.AccountID == key);
pi.PaymentInstrumentID = account.PayinPIs.Max(p => p.PaymentInstrumentID) + 1;
account.PayinPIs.Add(pi);
return Created(pi);
}

// PUT ~/Accounts(100)/PayoutPI
[ODataRoute("Accounts({accountId})/PayoutPI")]
public IHttpActionResult PutToPayoutPIFromAccount(int accountId, [FromBody] PaymentInstrument paymentInstrument)
{
var account = _accounts.Single(a => a.AccountID == accountId);
account.PayoutPI = paymentInstrument;
return Ok(paymentInstrument);
}

// PUT ~/Accounts(100)/PayinPIs(101)
[ODataRoute("Accounts({accountId})/PayinPIs({paymentInstrumentId})")]
public IHttpActionResult PutToPayinPI(int accountId, int paymentInstrumentId, [FromBody] PaymentInstrument paymentInstrument)
{
var account = _accounts.Single(a => a.AccountID == accountId);
var originalPi = account.PayinPIs.Single(p => p.PaymentInstrumentID == paymentInstrumentId);
originalPi.FriendlyName = paymentInstrument.FriendlyName;
return Ok(paymentInstrument);
}

// DELETE ~/Accounts(100)/PayinPIs(101)
[ODataRoute("Accounts({accountId})/PayinPIs({paymentInstrumentId})")]
public IHttpActionResult DeletePayinPIFromAccount(int accountId, int paymentInstrumentId)
{
var account = _accounts.Single(a => a.AccountID == accountId);
var originalPi = account.PayinPIs.Single(p => p.PaymentInstrumentID == paymentInstrumentId);
if (account.PayinPIs.Remove(originalPi))
{
return StatusCode(HttpStatusCode.NoContent);
}
return StatusCode(HttpStatusCode.InternalServerError);
}

// DELETE ~/Accounts(100)/PayinPIs(101)
[ODataRoute("Accounts({accountId})/PayoutPI")]
public IHttpActionResult DeletePayoutPIFromAccount(int accountId)
{
var account = _accounts.Single(a => a.AccountID == accountId);
account.PayoutPI = null;
return StatusCode(HttpStatusCode.NoContent);
}

// GET ~/Accounts(100)/PayinPIs/Namespace.GetCount)
[ODataRoute("Accounts({accountId})/PayinPIs/Containment.GetCount(NameContains={name})")]
public IHttpActionResult GetPayinPIsCountWhoseNameContainsGivenValue(int accountId, [FromODataUri] string name)
{
var account = _accounts.Single(a => a.AccountID == accountId);
var count = account.PayinPIs.Count(pi => pi.FriendlyName.Contains(name));

return Ok(count);
}

[ODataRoute("ResetDataSource")]
public IHttpActionResult ResetDataSource()
{
_accounts = InitAccounts();
return StatusCode(HttpStatusCode.NoContent);
}

private string GetServiceRootUri()
{
var routeName = Request.ODataProperties().RouteName;
var odataRoute = Configuration.Routes[routeName] as ODataRoute;
var prefixName = odataRoute.RoutePrefix;
var requestUri = Request.RequestUri.ToString();
var serviceRootUri = requestUri.Substring(0, requestUri.IndexOf(prefixName) + prefixName.Length);
return serviceRootUri;
}

private static IList<Account> InitAccounts()
{
var accounts = new List<Account>
{
new Account
{
AccountID = 100,
Name = "Name100",
PayoutPI = new PaymentInstrument
{
PaymentInstrumentID = 100,
FriendlyName = "Payout PI: Paypal"
},
PayinPIs = new List<PaymentInstrument>
{
new PaymentInstrument
{
PaymentInstrumentID = 101,
FriendlyName = "101 first PI"
},
new PaymentInstrument
{
PaymentInstrumentID = 102,
FriendlyName = "102 second PI"
}
}
}
};
return accounts;
}
}
}
65 changes: 65 additions & 0 deletions Swashbuckle.OData.Tests/Containment/ContainmentTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using System;
using System.Threading.Tasks;
using System.Web.OData.Extensions;
using FluentAssertions;
using Microsoft.Owin.Hosting;
using NUnit.Framework;
using Owin;
using Swashbuckle.Swagger;
using SwashbuckleODataSample;

namespace Swashbuckle.OData.Tests.Containment
{
[TestFixture]
public class ContainmentTests
{
[Test]
public async Task It_supports_models_that_use_containment()
{
using (WebApp.Start(HttpClientUtils.BaseAddress, appBuilder => Configuration(appBuilder, typeof(AccountsController))))
{
// Arrange
var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress, ODataConfig.ODataRoutePrefix);

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

// Assert
PathItem pathItem;
swaggerDocument.paths.TryGetValue("/odata/Accounts", out pathItem);
pathItem.Should().NotBeNull();

await ValidationUtils.ValidateSwaggerJson();
}
}

[Test]
public async Task It_supports_odata_attribute_routing()
{
using (WebApp.Start(HttpClientUtils.BaseAddress, appBuilder => Configuration(appBuilder, typeof(AccountsController))))
{
// Arrange
var httpClient = HttpClientUtils.GetHttpClient(HttpClientUtils.BaseAddress, ODataConfig.ODataRoutePrefix);

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

// Assert
PathItem pathItem;
swaggerDocument.paths.TryGetValue("/odata/Accounts({accountId})/PayinPIs({paymentInstrumentId})", out pathItem);
pathItem.Should().NotBeNull();

await ValidationUtils.ValidateSwaggerJson();
}
}

private static void Configuration(IAppBuilder appBuilder, Type targetController)
{
var config = appBuilder.GetStandardHttpConfig(targetController);

config.MapODataServiceRoute("odata", "odata", ODataModels.GetModel());

config.EnsureInitialized();
}
}
}
23 changes: 23 additions & 0 deletions Swashbuckle.OData.Tests/Containment/DataModels.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System.Collections.Generic;
using System.Web.OData.Builder;

namespace Containment
{
public class Account
{
public int AccountID { get; set; }
public string Name { get; set; }

[Contained]
public IList<PaymentInstrument> PayinPIs { get; set; }

[Contained]
public PaymentInstrument PayoutPI { get; set; }
}

public class PaymentInstrument
{
public int PaymentInstrumentID { get; set; }
public string FriendlyName { get; set; }
}
}
27 changes: 27 additions & 0 deletions Swashbuckle.OData.Tests/Containment/ODataModels.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System.Web.OData.Builder;
using Containment;
using Microsoft.OData.Edm;

namespace Swashbuckle.OData.Tests.Containment
{
public class ODataModels
{
public static IEdmModel GetModel()
{
var builder = new ODataConventionModelBuilder();
var paymentInstrumentType = builder.EntityType<PaymentInstrument>();

builder.EntitySet<Account>("Accounts");

var functionConfiguration = paymentInstrumentType.Collection.Function("GetCount");
functionConfiguration.Parameter<string>("NameContains");
functionConfiguration.Returns<int>();

builder.Action("ResetDataSource");

builder.Namespace = typeof (Account).Namespace;

return builder.GetEdmModel();
}
}
}
Loading

0 comments on commit 690c836

Please sign in to comment.