Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow programmatic configuration of SwaggerEndpoints #105

Merged
merged 2 commits into from
Jun 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,15 +100,15 @@ services.AddSwaggerForOcelot(Configuration);
6. In `Configure` method, insert the `SwaggerForOcelot` middleware to expose interactive documentation.

```CSharp
app.UseSwaggerForOcelotUI(Configuration, opt => {
app.UseSwaggerForOcelotUI(opt => {
opt.PathToSwaggerGenerator = "/swagger/docs";
})
```

You can optionally include headers that your Ocelot Gateway will send when requesting a swagger endpoint. This can be especially useful if your downstream microservices require contents from a header to authenticate.

```CSharp
app.UseSwaggerForOcelotUI(Configuration, opt => {
app.UseSwaggerForOcelotUI(opt => {
opts.DownstreamSwaggerHeaders = new[]
{
new KeyValuePair<string, string>("Auth-Key", "AuthValue"),
Expand All @@ -126,13 +126,13 @@ public string AlterUpstreamSwaggerJson(HttpContext context, string swaggerJson)
return swagger.ToString(Formatting.Indented);
}

app.UseSwaggerForOcelotUI(Configuration, opt => {
app.UseSwaggerForOcelotUI(opt => {
opts.ReConfigureUpstreamSwaggerJson = AlterUpstreamSwaggerJson;
})
```
You can optionally customize the swagger server prior to calling the endpoints of the microservices as follows:
```CSharp
app.UseSwaggerForOcelotUI(Configuration, opt => {
app.UseSwaggerForOcelotUI(opt => {
opts.ReConfigureUpstreamSwaggerJson = AlterUpstreamSwaggerJson;
opts.ServerOcelot = "/siteName/apigateway" ;
})
Expand Down
2 changes: 1 addition & 1 deletion demo/ApiGateway/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
endpoints.MapControllers();
});
app.UseStaticFiles();
app.UseSwaggerForOcelotUI(Configuration, opt =>
app.UseSwaggerForOcelotUI(opt =>
{
opt.DownstreamSwaggerHeaders = new[]
{
Expand Down
4 changes: 2 additions & 2 deletions demo/ApiGatewayWithPath/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
app.UseDeveloperExceptionPage();
}
app.UseStaticFiles();
app.UseSwaggerForOcelotUI(Configuration, opt =>
app.UseSwaggerForOcelotUI(opt =>
{
opt.DownstreamSwaggerEndPointBasePath = "/gateway/swagger/docs";
opt.PathToSwaggerGenerator = "/swagger/docs";
Expand All @@ -44,4 +44,4 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
.Wait();
}
}
}
}
63 changes: 28 additions & 35 deletions src/MMLib.SwaggerForOcelot/Middleware/BuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
using Swashbuckle.AspNetCore.SwaggerUI;
using System;
using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;

namespace Microsoft.AspNetCore.Builder
{
Expand All @@ -16,48 +18,54 @@ public static class BuilderExtensions
/// Add Swagger generator for downstream services and UI into application pipeline.
/// </summary>
/// <param name="app">The application builder.</param>
/// <param name="configuration">The configuration.</param>
/// <returns>
/// <see cref="IApplicationBuilder"/>.
/// </returns>
public static IApplicationBuilder UseSwaggerForOcelotUI(
this IApplicationBuilder app,
IConfiguration configuration)
=> app.UseSwaggerForOcelotUI(configuration, null);

/// <summary>
/// Add Swagger generator for downstream services and UI into application pipeline.
/// </summary>
/// <param name="app">The application builder.</param>
/// <param name="configuration">The configuration.</param>
/// <param name="setupAction">Setup <see cref="SwaggerForOcelotUIOptions"/></param>
/// <returns>
/// <see cref="IApplicationBuilder"/>.
/// </returns>
public static IApplicationBuilder UseSwaggerForOcelotUI(
this IApplicationBuilder app,
IConfiguration configuration,
Action<SwaggerForOcelotUIOptions> setupAction)
Action<SwaggerForOcelotUIOptions> setupAction = null)
{
var options = new SwaggerForOcelotUIOptions();
SwaggerForOcelotUIOptions options = app.ApplicationServices.GetService<IOptions<SwaggerForOcelotUIOptions>>().Value;
setupAction?.Invoke(options);
UseSwaggerForOcelot(app, options);

app.UseSwaggerUI(c =>
{
InitUIOption(c, options);
IEnumerable<SwaggerEndPointOptions> endPoints = GetConfiguration(configuration);
IReadOnlyList<SwaggerEndPointOptions> endPoints = app.ApplicationServices.GetService<IOptions<List<SwaggerEndPointOptions>>>().Value;
AddSwaggerEndPoints(c, endPoints, options.DownstreamSwaggerEndPointBasePath);
});

return app;
}

/// <inheritdoc cref="UseSwaggerForOcelotUI(IApplicationBuilder,Action{SwaggerForOcelotUIOptions})"/>
[Obsolete("Use app.UseSwaggerForOcelotUI() instead.")]
public static IApplicationBuilder UseSwaggerForOcelotUI(
this IApplicationBuilder app,
IConfiguration configuration)
=> app.UseSwaggerForOcelotUI();

/// <inheritdoc cref="UseSwaggerForOcelotUI(IApplicationBuilder,Action{SwaggerForOcelotUIOptions})"/>
[Obsolete("Use app.UseSwaggerForOcelotUI(setupAction) instead.")]
public static IApplicationBuilder UseSwaggerForOcelotUI(
this IApplicationBuilder app,
IConfiguration configuration,
Action<SwaggerForOcelotUIOptions> setupAction)
=> app.UseSwaggerForOcelotUI(setupAction);

private static void UseSwaggerForOcelot(IApplicationBuilder app, SwaggerForOcelotUIOptions options)
=> app.Map(options.PathToSwaggerGenerator, builder => builder.UseMiddleware<SwaggerForOcelotMiddleware>(options));

private static void AddSwaggerEndPoints(SwaggerUIOptions c, IEnumerable<SwaggerEndPointOptions> endPoints, string basePath)
private static void AddSwaggerEndPoints(SwaggerUIOptions c, IReadOnlyList<SwaggerEndPointOptions> endPoints, string basePath)
{
if (endPoints is null || endPoints.Count == 0)
{
throw new InvalidOperationException(
$"{SwaggerEndPointOptions.ConfigurationSectionName} configuration section is missing or empty.");
}

foreach (SwaggerEndPointOptions endPoint in endPoints)
{
foreach (SwaggerEndPointConfig config in endPoint.Config)
Expand All @@ -76,20 +84,5 @@ private static void InitUIOption(SwaggerUIOptions c, SwaggerForOcelotUIOptions o
c.OAuthConfigObject = options.OAuthConfigObject;
c.RoutePrefix = options.RoutePrefix;
}

private static IEnumerable<SwaggerEndPointOptions> GetConfiguration(IConfiguration configuration)
{
IEnumerable<SwaggerEndPointOptions> options =
configuration.GetSection(SwaggerEndPointOptions.ConfigurationSectionName)
.Get<IEnumerable<SwaggerEndPointOptions>>();

if (options is null)
{
throw new InvalidOperationException(
$"{SwaggerEndPointOptions.ConfigurationSectionName} configuration section is missing or empty.");
}

return options;
}
}
}
}
76 changes: 70 additions & 6 deletions tests/MMLib.SwaggerForOcelot.Tests/BuilderExtensionsShould.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,88 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using MMLib.SwaggerForOcelot.Configuration;
using Newtonsoft.Json.Linq;
using Swashbuckle.AspNetCore.SwaggerUI;
using System;
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;
using System.Linq;
using Xunit;

namespace MMLib.SwaggerForOcelot.Tests
{
public class BuilderExtensionsShould
{
private const string TestEndpointUrl = "http://localhost:5100/swagger/v1/swagger.json";
private static readonly SwaggerEndPointOptions _testSwaggerEndpointOptions = new SwaggerEndPointOptions
{
Key = "contacts",
Config = new List<SwaggerEndPointConfig>
{
new SwaggerEndPointConfig
{
Name = "Contacts API",
Version = "v1",
Url = TestEndpointUrl,
},
},
};

[Fact]
public void ThrowWhenSwaggerEndPointsSectionIsMissing() => TestWithInvalidConfiguration("{}");

[Fact]
public void ThrowWhenSwaggerEndPointsSectionIsEmpty()
=> TestWithInvalidConfiguration($"{{\"{SwaggerEndPointOptions.ConfigurationSectionName}\":[]}}");

private void TestWithInvalidConfiguration(string jsonConfiguration)
[Fact]
public void UseSwaggerEndpointsFromConfigurationFile()
{
var configJson = new JObject
{
[SwaggerEndPointOptions.ConfigurationSectionName] = new JArray
{
JObject.FromObject(_testSwaggerEndpointOptions),
},
};

TestWithValidConfiguration(configJson.ToString());
}

[Fact]
public void UseSwaggerEndpointsFromProgrammaticConfiguration()
{
void configureServices(IServiceCollection services)
{
services.Configure<List<SwaggerEndPointOptions>>(options =>
{
options.Add(_testSwaggerEndpointOptions);
});
}

TestWithValidConfiguration("{}", configureServices);
}

private void TestWithValidConfiguration(string jsonConfiguration, Action<IServiceCollection> configureServices = null)
{
IApplicationBuilder builder = GetApplicationBuilderWithJsonConfiguration(jsonConfiguration, configureServices);
Action action = () => builder.UseSwaggerForOcelotUI();

action.Should().NotThrow();
}

private IApplicationBuilder GetApplicationBuilderWithJsonConfiguration(string jsonConfiguration, Action<IServiceCollection> configureServices = null)
{
IConfiguration config = GetConfiguration(jsonConfiguration);
IApplicationBuilder builder = GetApplicationBuilder(config);
Action action = () => { builder.UseSwaggerForOcelotUI(config); };
return GetApplicationBuilder(config, configureServices);
}

private void TestWithInvalidConfiguration(string jsonConfiguration)
{
IApplicationBuilder builder = GetApplicationBuilderWithJsonConfiguration(jsonConfiguration);
Action action = () => builder.UseSwaggerForOcelotUI();

action.Should().Throw<InvalidOperationException>();
}
Expand All @@ -38,9 +98,13 @@ private IConfiguration GetConfiguration(string jsonConfiguration)
return configuration;
}

private IApplicationBuilder GetApplicationBuilder(IConfiguration configuration)
private IApplicationBuilder GetApplicationBuilder(IConfiguration configuration, Action<IServiceCollection> configureServices = null)
{
IServiceProvider serviceProvider = new ServiceCollection().AddSwaggerForOcelot(configuration).BuildServiceProvider();
IServiceCollection serviceCollection = new ServiceCollection();

serviceCollection.AddSwaggerForOcelot(configuration);
configureServices?.Invoke(serviceCollection);
IServiceProvider serviceProvider = serviceCollection.BuildServiceProvider();

return new ApplicationBuilder(serviceProvider);
}
Expand Down