Permalink
Browse files

Add `IDocumentProvider` service and its implementation (#1678)

* Add `IDocumentProvider` service and its implementation
- #1626
- does not "provide an api to generate multiple / all documents"; could be layered on this work
  - otherwise, meets requirements listed in #1626
- assumes the following answers to open questions in #1626 (generally means everything is opt-in)
  - without an `AddSwagger()` call, `IDocumentProvider` service will not exist
  - registered document name / identifier is new `SwaggerSettings<>.DocumentName` property
    - document is not registered unless `DocumentName` is non-`null` i.e. `IDocumentProvider` won't know about it
    - `SwaggerSettings<>.MiddlewareBasePath` and `SwaggerRoute` are independent of `DocumentName`
  - middleware does not depend on the `IDocumentProvider` or registrar services; duplicating some code
- many thanks to @rynowak for writing this proposal

* Revert changes to support `WebApiToSwaggerGeneratorSettings` in `IDocumentProvider`
- remove some code from files added in 5b7e0a3b34
- revert changes made in 5b7e0a3b34 to existing files

* Make `IDocumentPovider` `public`

* Set `SwaggerSettings<T>.DocumentName` to `"v1"` by default

* nit: Correct doc comment in `SwaggerExtensions`

* Remove `RegisteredDocument.ControllerTypes`

* nit: Clean up some `using`s

* nit: Ignore VS Code files

* Revert "Make `IDocumentPovider` `public`"
- `internal` again
- commit 4057107.

* Move method

* Improve middleware api

* Refactoring

* Improve comments

* Improve api

* Improve sample

* Minor fix

* Move method

* WIP: Add ability to register documents from `Startup.ConfigureServices(...)`
- add NSwag.Sample.OpenApi project
- rework bd9c7fc; revert part of that commit e.g. undo `SwaggerExtensions` changes

nit:
- correct doc comment for `AspNetCoreToSwaggerGeneratorSettings`

WIP:
- some doc comments are currently empty
- names are tentative

* Add ISwaggerGenerator

* Update startup

* Remove unused files

* Make AddDocument extension method and use ISwaggerDocumentBuilder

* Update sln

* Refactoring

* Move method

* Add documentName support

* Remove unused usings
  • Loading branch information...
RSuter committed Oct 26, 2018
1 parent 7f34dc4 commit 914e36b24a329946ea4bcddcc0cd69ad11f93932
Showing with 643 additions and 278 deletions.
  1. +2 −0 .gitignore
  2. +156 −0 src/NSwag.AspNetCore/Extensions/NSwagApplicationBuilderExtensions .cs
  3. +68 −0 src/NSwag.AspNetCore/Extensions/NSwagServiceCollectionExtensions.cs
  4. +20 −0 src/NSwag.AspNetCore/IDocumentProvider.cs
  5. +17 −0 src/NSwag.AspNetCore/ISwaggerDocumentBuilder.cs
  6. +38 −40 src/NSwag.AspNetCore/Middlewares/{AspNetCoreToSwaggerMiddleware.cs → SwaggerMiddleware.cs}
  7. +34 −0 src/NSwag.AspNetCore/Middlewares/SwaggerMiddlewareSettings.cs
  8. +3 −3 src/NSwag.AspNetCore/{NSwagConfigureMvcOptions.cs → SwaggerConfigureMvcOptions.cs}
  9. +63 −0 src/NSwag.AspNetCore/SwaggerDocumentProvider.cs
  10. +48 −0 src/NSwag.AspNetCore/SwaggerDocumentRegistry.cs
  11. +15 −0 src/NSwag.AspNetCore/SwaggerDocumentSettings.cs
  12. +42 −111 src/NSwag.AspNetCore/SwaggerExtensions.cs
  13. +1 −1 ...Swag.AspNetCore/{NSwagMvcApplicationModelConvention.cs → SwaggerMvcApplicationModelConvention.cs}
  14. +8 −7 src/NSwag.AspNetCore/SwaggerSettings.cs
  15. +1 −0 src/NSwag.Min.sln
  16. +10 −38 src/NSwag.Sample.NETCore11/Startup.cs
  17. +1 −1 src/NSwag.Sample.NETCore20/Properties/launchSettings.json
  18. +53 −63 src/NSwag.Sample.NETCore20/Startup.cs
  19. +1 −1 src/NSwag.Sample.NETCore21/Properties/launchSettings.json
  20. +13 −5 src/NSwag.Sample.NETCore21/Startup.cs
  21. +22 −3 src/NSwag.SwaggerGeneration.AspNetCore/AspNetCoreToSwaggerGenerator.cs
  22. +1 −1 src/NSwag.SwaggerGeneration.AspNetCore/AspNetCoreToSwaggerGeneratorSettings.cs
  23. +1 −0 src/NSwag.SwaggerGeneration.AspNetCore/NSwag.SwaggerGeneration.AspNetCore.csproj
  24. +3 −3 src/NSwag.SwaggerGeneration.AspNetCore/Processors/OperationResponseProcessor.cs
  25. +21 −0 src/NSwag.SwaggerGeneration/ISwaggerGenerator.cs
  26. +1 −1 src/NSwag.sln
@@ -65,3 +65,5 @@ NSwagStudio*.nupkg
/samples/WithoutMiddleware/Sample.AspNetCore20/.vs/Sample.AspNetCore20/v15/Server/sqlite3
/samples/WithoutMiddleware/Sample.AspNetCore20/.vs/Sample.AspNetCore20/DesignTimeBuild
/samples/.vs/*
.vscode/
/src/.cr/*
@@ -0,0 +1,156 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Options;
using NSwag.AspNetCore;
using NSwag.AspNetCore.Middlewares;
using NSwag.SwaggerGeneration;
using NSwag.SwaggerGeneration.WebApi;
using System;
using System.Collections.Generic;
using System.Reflection;
namespace Microsoft.AspNetCore.Builder
{
/// <summary>NSwag extensions for <see cref="IApplicationBuilder"/>.</summary>
public static class NSwagApplicationBuilderExtensions
{
/// <summary>Adds the OpenAPI/Swagger generator that uses Api Description to perform Swagger generation.</summary>
/// <remarks>Registers multiple routes/documents if the settings.Path contains a '{documentName}' placeholder.</remarks>
/// <param name="app">The app.</param>
/// <param name="configure">Configure additional settings.</param>
public static IApplicationBuilder UseSwagger(this IApplicationBuilder app, Action<SwaggerMiddlewareSettings> configure = null)
{
return UseSwaggerWithApiExplorerCore(app, configure);
}
private static IApplicationBuilder UseSwaggerWithApiExplorerCore(IApplicationBuilder app, Action<SwaggerMiddlewareSettings> configure)
{
// TODO(v12): Add IOptions support when SwaggerUi3Settings<> T has been removed
//var settings = configure == null && app.ApplicationServices.GetService<IOptions<SwaggerMiddlewareSettings>>()?.Value ?? new SwaggerMiddlewareSettings();
var settings = new SwaggerMiddlewareSettings();
configure?.Invoke(settings);
if (settings.Path.Contains("{documentName}"))
{
var registry = app.ApplicationServices.GetRequiredService<SwaggerDocumentRegistry>();
foreach (var document in registry.Documents)
{
app = app.UseMiddleware<SwaggerMiddleware>(document.Key, settings.Path.Replace("{documentName}", document.Key), settings);
}
return app;
}
else
{
return app.UseMiddleware<SwaggerMiddleware>(settings.DocumentName, settings.Path, settings);
}
}
/// <summary>Adds the Swagger UI (only) to the pipeline.</summary>
/// <remarks>The settings.GeneratorSettings property does not have any effect.</remarks>
/// <param name="app">The app.</param>
/// <param name="configure">Configure the Swagger settings.</param>
/// <returns>The app builder.</returns>
public static IApplicationBuilder UseSwaggerUi3(
this IApplicationBuilder app,
Action<SwaggerUi3Settings<WebApiToSwaggerGeneratorSettings>> configure = null)
{
// TODO(v12): Add IOptions support when SwaggerUi3Settings<> T has been removed
//var settings = configure == null && app.ApplicationServices.GetService<IOptions<SwaggerUi3Settings<WebApiToSwaggerGeneratorSettings>>>()?.Value ??
// new SwaggerUi3Settings<WebApiToSwaggerGeneratorSettings>();
var settings = new SwaggerUi3Settings<WebApiToSwaggerGeneratorSettings>();
configure?.Invoke(settings);
UseSwaggerUiWithDocumentNamePlaceholderExpanding(app, settings, (swaggerRoute, swaggerUiRoute) =>
{
app.UseMiddleware<RedirectMiddleware>(swaggerUiRoute, swaggerRoute);
app.UseMiddleware<SwaggerUiIndexMiddleware<WebApiToSwaggerGeneratorSettings>>(swaggerUiRoute + "/index.html", settings, "NSwag.AspNetCore.SwaggerUi3.index.html");
app.UseFileServer(new FileServerOptions
{
RequestPath = new PathString(settings.ActualSwaggerUiRoute),
FileProvider = new EmbeddedFileProvider(typeof(SwaggerExtensions).GetTypeInfo().Assembly, "NSwag.AspNetCore.SwaggerUi3")
});
},
(documents) =>
{
var swaggerRouteWithPlaceholder = settings.ActualSwaggerRoute;
settings.SwaggerRoutes.Clear();
foreach (var document in documents)
{
var swaggerRoute = swaggerRouteWithPlaceholder.Replace("{documentName}", document.Key);
settings.SwaggerRoutes.Add(new SwaggerUi3Route(document.Key, swaggerRoute));
}
});
return app;
}
/// <summary>Adds the ReDoc UI (only) to the pipeline.</summary>
/// <remarks>The settings.GeneratorSettings property does not have any effect.</remarks>
/// <param name="app">The app.</param>
/// <param name="configure">Configure the Swagger settings.</param>
/// <returns>The app builder.</returns>
public static IApplicationBuilder UseReDoc(
this IApplicationBuilder app,
Action<SwaggerReDocSettings<WebApiToSwaggerGeneratorSettings>> configure = null)
{
var settings = new SwaggerReDocSettings<WebApiToSwaggerGeneratorSettings>();
configure?.Invoke(settings);
UseSwaggerUiWithDocumentNamePlaceholderExpanding(app, settings, (swaggerRoute, swaggerUiRoute) =>
{
app.UseMiddleware<RedirectMiddleware>(swaggerUiRoute, swaggerRoute);
app.UseMiddleware<SwaggerUiIndexMiddleware<WebApiToSwaggerGeneratorSettings>>(swaggerUiRoute + "/index.html", settings, "NSwag.AspNetCore.ReDoc.index.html");
app.UseFileServer(new FileServerOptions
{
RequestPath = new PathString(settings.ActualSwaggerUiRoute),
FileProvider = new EmbeddedFileProvider(typeof(SwaggerExtensions).GetTypeInfo().Assembly, "NSwag.AspNetCore.ReDoc")
});
}, (documents) => throw new NotSupportedException("ReDoc does not support multiple documents per UI: " +
"Do not use '{documentName}' placeholder only in SwaggerRoute but also in SwaggerUiRoute to register multiple UIs."));
return app;
}
private static void UseSwaggerUiWithDocumentNamePlaceholderExpanding(IApplicationBuilder app,
SwaggerUiSettingsBase<WebApiToSwaggerGeneratorSettings> settings,
Action<string, string> register,
Action<IReadOnlyDictionary<string, ISwaggerGenerator>> registerMultiple)
{
if (settings.ActualSwaggerRoute.Contains("{documentName}"))
{
var registry = app.ApplicationServices.GetRequiredService<SwaggerDocumentRegistry>();
if (settings.ActualSwaggerUiRoute.Contains("{documentName}"))
{
// Register multiple uis
foreach (var document in registry.Documents)
{
register(
settings.ActualSwaggerRoute.Replace("{documentName}", document.Key),
settings.ActualSwaggerUiRoute.Replace("{documentName}", document.Key));
}
}
else
{
// Register single ui with multiple documents
registerMultiple(registry.Documents);
register(settings.ActualSwaggerRoute, settings.ActualSwaggerUiRoute);
}
}
else
{
if (settings.ActualSwaggerUiRoute.Contains("{documentName}"))
{
throw new ArgumentException("The SwaggerUiRoute cannot contain '{documentName}' placeholder when SwaggerRoute is missing the placeholder.");
}
register(settings.ActualSwaggerRoute, settings.ActualSwaggerUiRoute);
}
}
}
}
@@ -0,0 +1,68 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using NJsonSchema;
using NSwag.AspNetCore;
using NSwag.SwaggerGeneration;
using NSwag.SwaggerGeneration.AspNetCore;
using System;
namespace Microsoft.Extensions.DependencyInjection
{
/// <summary>NSwag extensions for <see cref="IServiceCollection"/>.</summary>
public static class NSwagServiceCollectionExtensions
{
/// <summary>Adds services required for Swagger 2.0 generation (change document settings to generate OpenAPI 3.0).</summary>
/// <param name="serviceCollection">The <see cref="IServiceCollection"/>.</param>
/// <param name="configure">Configure the document registry.</param>
public static IServiceCollection AddSwagger(this IServiceCollection serviceCollection, Action<ISwaggerDocumentBuilder> configure = null)
{
if (configure == null)
{
configure = registry => registry.AddSwaggerDocument();
}
serviceCollection.AddSingleton(s =>
{
var registry = new SwaggerDocumentRegistry();
configure?.Invoke(registry);
return registry;
});
serviceCollection.AddSingleton<IConfigureOptions<MvcOptions>, SwaggerConfigureMvcOptions>();
serviceCollection.AddSingleton<SwaggerDocumentProvider>();
// Used by the Microsoft.Extensions.ApiDescription tool
serviceCollection.AddSingleton<ApiDescription.IDocumentProvider>(s => s.GetRequiredService<SwaggerDocumentProvider>());
return serviceCollection;
}
/// <summary>Adds a document to the registry.</summary>
/// <param name="registry">The registry.</param>
/// <param name="configure">The configure action.</param>
/// <returns>The registry.</returns>
public static ISwaggerDocumentBuilder AddOpenApiDocument(this ISwaggerDocumentBuilder registry, Action<SwaggerDocumentSettings> configure = null)
{
return AddSwaggerDocument(registry, settings =>
{
settings.SchemaType = SchemaType.OpenApi3;
configure?.Invoke(settings);
});
}
/// <summary>Adds a document to the registry.</summary>
/// <param name="registry">The registry.</param>
/// <param name="configure">The configure action.</param>
/// <returns>The registry.</returns>
public static ISwaggerDocumentBuilder AddSwaggerDocument(this ISwaggerDocumentBuilder registry, Action<SwaggerDocumentSettings> configure = null)
{
var settings = new SwaggerDocumentSettings();
settings.SchemaType = SchemaType.Swagger2;
configure?.Invoke(settings);
var generator = new AspNetCoreToSwaggerGenerator(settings, settings.SchemaGenerator ?? new SwaggerJsonSchemaGenerator(settings));
return ((SwaggerDocumentRegistry)registry).AddDocument(settings.DocumentName, generator);
}
}
}
@@ -0,0 +1,20 @@
//-----------------------------------------------------------------------
// <copyright file="IDocumentProvider.cs" company="NSwag">
// Copyright (c) Rico Suter. All rights reserved.
// </copyright>
// <license>https://github.com/NSwag/NSwag/blob/master/LICENSE.md</license>
// <author>Rico Suter, mail@rsuter.com</author>
//-----------------------------------------------------------------------
using System.IO;
using System.Threading.Tasks;
namespace Microsoft.Extensions.ApiDescription
{
// This service will be looked up by name from the service collection when using
// the Microsoft.Extensions.ApiDescription tool
internal interface IDocumentProvider
{
Task GenerateAsync(string documentName, TextWriter writer);
}
}
@@ -0,0 +1,17 @@
//-----------------------------------------------------------------------
// <copyright file="DocumentRegistry.cs" company="NSwag">
// Copyright (c) Rico Suter. All rights reserved.
// </copyright>
// <license>https://github.com/NSwag/NSwag/blob/master/LICENSE.md</license>
// <author>Rico Suter, mail@rsuter.com</author>
//-----------------------------------------------------------------------
using System;
namespace NSwag.AspNetCore
{
/// <summary>Used to add OpenAPI/Swagger documents to the registry.</summary>
public interface ISwaggerDocumentBuilder
{
}
}
Oops, something went wrong.

0 comments on commit 914e36b

Please sign in to comment.