This commit is contained in:
2025-04-21 14:10:27 +08:00
commit cb4c3b2935
2761 changed files with 1382589 additions and 0 deletions

View File

@@ -0,0 +1,106 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Description;
using System.Web.Http.Routing;
namespace Swashbuckle.Application
{
public class AreaApiExplorer:IApiExplorer
{
private IApiExplorer _innerApiExplorer;
private HttpConfiguration _configuration;
private Lazy<Collection<ApiDescription>> _apiDescriptions;
private MethodInfo _apiDescriptionPopulator;
public AreaApiExplorer(IApiExplorer apiExplorer, HttpConfiguration configuration)
{
_innerApiExplorer = apiExplorer;
_configuration = configuration;
_apiDescriptions=new Lazy<Collection<ApiDescription>>(new Func<Collection<ApiDescription>>(Init));
}
public Collection<ApiDescription> ApiDescriptions
{
get { return _apiDescriptions.Value; }
}
private Collection<ApiDescription> Init()
{
var descriptions = _innerApiExplorer.ApiDescriptions;
var controllerSelector = _configuration.Services.GetHttpControllerSelector();
var controllerMappings = controllerSelector.GetControllerMapping();
var flatRoutes = FlattenRoutes(_configuration.Routes);
var result=new Collection<ApiDescription>();
foreach (var description in descriptions)
{
result.Add(description);
if (controllerMappings != null)
{
var matchingRoutes =
flatRoutes.Where(
r => r.RouteTemplate == description.Route.RouteTemplate && r != description.Route);
foreach (var route in matchingRoutes)
{
GetRouteDescriptions(route,result);
}
}
}
return result;
}
private void GetRouteDescriptions(IHttpRoute route, Collection<ApiDescription> apiDescriptions)
{
var actionDescriptor = route.DataTokens["actions"] as IEnumerable<HttpActionDescriptor>;
if (actionDescriptor != null && actionDescriptor.Count() > 0)
{
GetPopulateMethod()
.Invoke(_innerApiExplorer,
new object[] {actionDescriptor.First(), route, route.RouteTemplate, apiDescriptions});
}
}
private MethodInfo GetPopulateMethod()
{
if (_apiDescriptionPopulator == null)
{
_apiDescriptionPopulator =
_innerApiExplorer.GetType()
.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)
.FirstOrDefault(m => m.Name == "PopulateActionDescriptions" && m.GetParameters().Length == 4);
}
return _apiDescriptionPopulator;
}
private static IEnumerable<IHttpRoute> FlattenRoutes(IEnumerable<IHttpRoute> routes)
{
var flatRoutes=new List<HttpRoute>();
foreach (var route in routes)
{
if (route is HttpRoute)
{
yield return route;
}
var subRoutes = route as IReadOnlyCollection<IHttpRoute>;
if (subRoutes != null)
{
foreach (var subRoute in FlattenRoutes(subRoutes))
{
yield return subRoute;
}
}
}
}
}
}

View File

@@ -0,0 +1,41 @@
using Swashbuckle.Swagger;
namespace Swashbuckle.Application
{
public class ContactBuilder
{
private string _name;
private string _url;
private string _email;
public ContactBuilder Name(string name)
{
_name = name;
return this;
}
public ContactBuilder Url(string url)
{
_url = url;
return this;
}
public ContactBuilder Email(string email)
{
_email = email;
return this;
}
internal Contact Build()
{
if ((_name ?? _url ?? _email) == null) return null;
return new Contact
{
name = _name,
url = _url,
email = _email
};
}
}
}

View File

@@ -0,0 +1,109 @@
using System;
using System.Linq;
using System.Web.Http;
using Newtonsoft.Json.Serialization;
using Swashbuckle.Application;
using System.Net.Http;
using System.Collections.Generic;
using System.Web.Http.Routing;
using Newtonsoft.Json;
namespace Swashbuckle.Application
{
public static class HttpConfigurationExtensions
{
private static readonly string DefaultRouteTemplate = "swagger/docs/{apiVersion}";
public static SwaggerEnabledConfiguration EnableSwagger(
this HttpConfiguration httpConfig,
Action<SwaggerDocsConfig> configure = null)
{
return EnableSwagger(httpConfig, DefaultRouteTemplate, configure);
}
public static SwaggerEnabledConfiguration EnableSwagger(
this HttpConfiguration httpConfig,
string routeTemplate,
Action<SwaggerDocsConfig> configure = null)
{
var config = new SwaggerDocsConfig();
if (configure != null) configure(config);
httpConfig.Routes.MapHttpRoute(
name: "swagger_docs" + routeTemplate,
routeTemplate: routeTemplate,
defaults: null,
constraints: new { apiVersion = @".+" },
handler: new SwaggerDocsHandler(config)
);
return new SwaggerEnabledConfiguration(
httpConfig,
config.GetRootUrl,
config.GetApiVersions().Select(version => routeTemplate.Replace("{apiVersion}", version)));
}
internal static JsonSerializerSettings SerializerSettingsOrDefault(this HttpConfiguration httpConfig)
{
var formatter = httpConfig.Formatters.JsonFormatter;
if (formatter != null)
return formatter.SerializerSettings;
return new JsonSerializerSettings();
}
}
public class SwaggerEnabledConfiguration
{
private static readonly string DefaultRouteTemplate = "swagger/ui/{*assetPath}";
private readonly HttpConfiguration _httpConfig;
private readonly Func<HttpRequestMessage, string> _rootUrlResolver;
private readonly IEnumerable<string> _discoveryPaths;
internal static IEnumerable<string> DiscoveryPaths;
public SwaggerEnabledConfiguration(
HttpConfiguration httpConfig,
Func<HttpRequestMessage, string> rootUrlResolver,
IEnumerable<string> discoveryPaths)
{
_httpConfig = httpConfig;
_rootUrlResolver = rootUrlResolver;
_discoveryPaths = discoveryPaths;
DiscoveryPaths = _discoveryPaths;
}
public void EnableSwaggerUi(Action<SwaggerUiConfig> configure = null)
{
EnableSwaggerUi(DefaultRouteTemplate, configure);
}
public void EnableSwaggerUi(
string routeTemplate,
Action<SwaggerUiConfig> configure = null)
{
var config = new SwaggerUiConfig(_discoveryPaths, _rootUrlResolver);
if (configure != null) configure(config);
_httpConfig.Routes.MapHttpRoute(
name: "swagger_ui" + routeTemplate,
routeTemplate: routeTemplate,
defaults: null,
constraints: new { assetPath = @".+" },
handler: new SwaggerUiHandler(config)
);
if (routeTemplate == DefaultRouteTemplate)
{
_httpConfig.Routes.MapHttpRoute(
name: "swagger_ui_shortcut",
routeTemplate: "swagger",
defaults: null,
constraints: new { uriResolution = new HttpRouteDirectionConstraint(HttpRouteDirection.UriResolution) },
handler: new RedirectHandler(_rootUrlResolver, "swagger/ui/index"));
}
}
}
}

View File

@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Web.Http.Routing;
namespace Swashbuckle.Application
{
public class HttpRouteDirectionConstraint : IHttpRouteConstraint
{
private readonly HttpRouteDirection _allowedDirection;
public HttpRouteDirectionConstraint(HttpRouteDirection allowedDirection)
{
_allowedDirection = allowedDirection;
}
public bool Match(
HttpRequestMessage request,
IHttpRoute route,
string parameterName,
IDictionary<string, object> values,
HttpRouteDirection routeDirection)
{
return routeDirection == _allowedDirection;
}
}
}

View File

@@ -0,0 +1,68 @@
using System;
using Swashbuckle.Swagger;
namespace Swashbuckle.Application
{
public class InfoBuilder
{
private string _version;
private string _title;
private string _description;
private string _termsOfService;
/// <summary>
/// 是否默认路由
/// </summary>
private bool _isDefaultRoute;
private readonly ContactBuilder _contactBuilder = new ContactBuilder();
private readonly LicenseBuilder _licenseBuilder = new LicenseBuilder();
public InfoBuilder(string version, string title)
{
_version = version;
_title = title;
}
public InfoBuilder(string version, string title, bool isDefualt):this(version,title)
{
_isDefaultRoute = isDefualt;
}
public InfoBuilder Description(string description)
{
_description = description;
return this;
}
public InfoBuilder TermsOfService(string termsOfService)
{
_termsOfService = termsOfService;
return this;
}
public InfoBuilder Contact(Action<ContactBuilder> contact)
{
contact(_contactBuilder);
return this;
}
public InfoBuilder License(Action<LicenseBuilder> license)
{
license(_licenseBuilder);
return this;
}
internal Info Build()
{
return new Info
{
version = _version,
title = _title,
description = _description,
termsOfService = _termsOfService,
contact = _contactBuilder.Build(),
license = _licenseBuilder.Build(),
isDefaultRoute = _isDefaultRoute
};
}
}
}

View File

@@ -0,0 +1,33 @@
using Swashbuckle.Swagger;
namespace Swashbuckle.Application
{
public class LicenseBuilder
{
private string _name;
private string _url;
public LicenseBuilder Name(string name)
{
_name = name;
return this;
}
public LicenseBuilder Url(string url)
{
_url = url;
return this;
}
internal License Build()
{
if ((_name ?? _url) == null) return null;
return new License
{
name = _name,
url = _url
};
}
}
}

View File

@@ -0,0 +1,32 @@
using System;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace Swashbuckle.Application
{
public class RedirectHandler : HttpMessageHandler
{
private readonly Func<HttpRequestMessage, string> _rootUrlResolver;
private readonly string _redirectPath;
public RedirectHandler(Func<HttpRequestMessage, string> rootUrlResolver, string redirectPath)
{
_rootUrlResolver = rootUrlResolver;
_redirectPath = redirectPath;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var redirectUrl = _rootUrlResolver(request) + "/" + _redirectPath;
var response = request.CreateResponse(HttpStatusCode.Moved);
response.Headers.Location = new Uri(redirectUrl);
var tsc = new TaskCompletionSource<HttpResponseMessage>();
tsc.SetResult(response);
return tsc.Task;
}
}
}

View File

@@ -0,0 +1,122 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Swashbuckle.Swagger;
namespace Swashbuckle.Application
{
public abstract class SecuritySchemeBuilder
{
internal abstract SecurityScheme Build();
}
public class BasicAuthSchemeBuilder : SecuritySchemeBuilder
{
private string _description;
public BasicAuthSchemeBuilder Description(string description)
{
_description = description;
return this;
}
internal override SecurityScheme Build()
{
return new SecurityScheme
{
type = "basic",
description = _description
};
}
}
public class ApiKeySchemeBuilder : SecuritySchemeBuilder
{
private string _description;
private string _name;
private string _in;
public ApiKeySchemeBuilder Description(string description)
{
_description = description;
return this;
}
public ApiKeySchemeBuilder Name(string name)
{
_name = name;
return this;
}
public ApiKeySchemeBuilder In(string @in)
{
_in = @in;
return this;
}
internal override SecurityScheme Build()
{
return new SecurityScheme
{
type = "apiKey",
description = _description,
name = _name,
@in = _in
};
}
}
public class OAuth2SchemeBuilder : SecuritySchemeBuilder
{
private string _description;
private string _flow;
private string _authorizationUrl;
private string _tokenUrl;
private IDictionary<string, string> _scopes = new Dictionary<string, string>();
public OAuth2SchemeBuilder Description(string description)
{
_description = description;
return this;
}
public OAuth2SchemeBuilder Flow(string flow)
{
_flow = flow;
return this;
}
public OAuth2SchemeBuilder AuthorizationUrl(string authorizationUrl)
{
_authorizationUrl = authorizationUrl;
return this;
}
public OAuth2SchemeBuilder TokenUrl(string tokenUrl)
{
_tokenUrl = tokenUrl;
return this;
}
public OAuth2SchemeBuilder Scopes(Action<IDictionary<string, string>> configure)
{
configure(_scopes);
return this;
}
internal override SecurityScheme Build()
{
// TODO: Validate required fields for given flow
return new SecurityScheme
{
type = "oauth2",
flow = _flow,
authorizationUrl = _authorizationUrl,
tokenUrl = _tokenUrl,
scopes = _scopes,
description = _description,
};
}
}
}

View File

@@ -0,0 +1,335 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Web.Http;
using System.Web.Http.Description;
using System.Xml.XPath;
using Newtonsoft.Json;
using Swashbuckle.Swagger;
using Swashbuckle.Swagger.Annotations;
using Swashbuckle.Swagger.FromUriParams;
using Swashbuckle.Swagger.XmlComments;
namespace Swashbuckle.Application
{
public class SwaggerDocsConfig
{
private VersionInfoBuilder _versionInfoBuilder;
private Func<ApiDescription, string, bool> _versionSupportResolver;
private IEnumerable<string> _schemes;
private IDictionary<string, SecuritySchemeBuilder> _securitySchemeBuilders;
private bool _prettyPrint;
private bool _ignoreObsoleteActions;
private Func<ApiDescription, string> _groupingKeySelector;
private IComparer<string> _groupingKeyComparer;
private readonly IDictionary<Type, Func<Schema>> _customSchemaMappings;
private readonly IList<Func<ISchemaFilter>> _schemaFilters;
private readonly IList<Func<IModelFilter>> _modelFilters;
private Func<Type, string> _schemaIdSelector;
private bool _ignoreObsoleteProperties;
private bool _describeAllEnumsAsStrings;
private bool _describeStringEnumsInCamelCase;
private bool _applyFiltersToAllSchemas;
private readonly IList<Func<IOperationFilter>> _operationFilters;
private readonly IList<Func<IDocumentFilter>> _documentFilters;
private readonly IList<Func<XPathDocument>> _xmlDocFactories;
private Func<IEnumerable<ApiDescription>, ApiDescription> _conflictingActionsResolver;
private Func<HttpRequestMessage, string> _rootUrlResolver;
private Func<ISwaggerProvider, ISwaggerProvider> _customProviderFactory;
/// <summary>
/// 是否显示开发者信息
/// </summary>
internal static bool ShowDeveloper = false;
public SwaggerDocsConfig()
{
_versionInfoBuilder = new VersionInfoBuilder();
_securitySchemeBuilders = new Dictionary<string, SecuritySchemeBuilder>();
_prettyPrint = false;
_ignoreObsoleteActions = false;
_customSchemaMappings = new Dictionary<Type, Func<Schema>>();
_schemaFilters = new List<Func<ISchemaFilter>>();
_modelFilters = new List<Func<IModelFilter>>();
_ignoreObsoleteProperties = false;
_describeAllEnumsAsStrings = false;
_describeStringEnumsInCamelCase = false;
_applyFiltersToAllSchemas = false;
_operationFilters = new List<Func<IOperationFilter>>();
_documentFilters = new List<Func<IDocumentFilter>>();
_xmlDocFactories = new List<Func<XPathDocument>>();
_rootUrlResolver = DefaultRootUrlResolver;
SchemaFilter<ApplySwaggerSchemaFilterAttributes>();
OperationFilter<HandleFromUriParams>();
OperationFilter<ApplySwaggerOperationAttributes>();
OperationFilter<ApplySwaggerResponseAttributes>();
OperationFilter<ApplySwaggerOperationFilterAttributes>();
}
public InfoBuilder SingleApiVersion(string version, string title)
{
_versionSupportResolver = null;
_versionInfoBuilder = new VersionInfoBuilder();
return _versionInfoBuilder.Version(version, title, true);
}
public void MultipleApiVersions(
Func<ApiDescription, string, bool> versionSupportResolver,
Action<VersionInfoBuilder> configure)
{
_versionSupportResolver = versionSupportResolver;
_versionInfoBuilder = new VersionInfoBuilder();
configure(_versionInfoBuilder);
}
public void Schemes(IEnumerable<string> schemes)
{
_schemes = schemes;
}
public BasicAuthSchemeBuilder BasicAuth(string name)
{
var schemeBuilder = new BasicAuthSchemeBuilder();
_securitySchemeBuilders[name] = schemeBuilder;
return schemeBuilder;
}
public ApiKeySchemeBuilder ApiKey(string name)
{
var schemeBuilder = new ApiKeySchemeBuilder();
_securitySchemeBuilders[name] = schemeBuilder;
return schemeBuilder;
}
public OAuth2SchemeBuilder OAuth2(string name)
{
var schemeBuilder = new OAuth2SchemeBuilder();
_securitySchemeBuilders[name] = schemeBuilder;
return schemeBuilder;
}
public void PrettyPrint()
{
_prettyPrint = true;
}
public void IgnoreObsoleteActions()
{
_ignoreObsoleteActions = true;
}
public void GroupActionsBy(Func<ApiDescription, string> keySelector)
{
_groupingKeySelector = keySelector;
}
public void OrderActionGroupsBy(IComparer<string> keyComparer)
{
_groupingKeyComparer = keyComparer;
}
public void MapType<T>(Func<Schema> factory)
{
MapType(typeof(T), factory);
}
public void MapType(Type type, Func<Schema> factory)
{
_customSchemaMappings.Add(type, factory);
}
public void SchemaFilter<TFilter>()
where TFilter : ISchemaFilter, new()
{
SchemaFilter(() => new TFilter());
}
public void SchemaFilter(Func<ISchemaFilter> factory)
{
_schemaFilters.Add(factory);
}
// NOTE: In next major version, ModelFilter will completely replace SchemaFilter
internal void ModelFilter<TFilter>()
where TFilter : IModelFilter, new()
{
ModelFilter(() => new TFilter());
}
// NOTE: In next major version, ModelFilter will completely replace SchemaFilter
internal void ModelFilter(Func<IModelFilter> factory)
{
_modelFilters.Add(factory);
}
public void SchemaId(Func<Type, string> schemaIdStrategy)
{
_schemaIdSelector = schemaIdStrategy;
}
public void UseFullTypeNameInSchemaIds()
{
_schemaIdSelector = t => t.FriendlyId(true);
}
public void DescribeAllEnumsAsStrings(bool camelCase = false)
{
_describeAllEnumsAsStrings = true;
_describeStringEnumsInCamelCase = camelCase;
}
public void IgnoreObsoleteProperties()
{
_ignoreObsoleteProperties = true;
}
[Obsolete("This will be removed in 6.0.0; it will always be true.")]
public void ApplyFiltersToAllSchemas()
{
_applyFiltersToAllSchemas = true;
}
public void OperationFilter<TFilter>()
where TFilter : IOperationFilter, new()
{
OperationFilter(() => new TFilter());
}
public void OperationFilter(Func<IOperationFilter> factory)
{
_operationFilters.Add(factory);
}
public void DocumentFilter<TFilter>()
where TFilter : IDocumentFilter, new()
{
DocumentFilter(() => new TFilter());
}
public void DocumentFilter(Func<IDocumentFilter> factory)
{
_documentFilters.Add(factory);
}
public void IncludeXmlComments(Func<XPathDocument> xmlDocFactory)
{
_xmlDocFactories.Add(xmlDocFactory);
}
public void IncludeXmlComments(string filePath)
{
_xmlDocFactories.Add(() => new XPathDocument(filePath));
}
public void ResolveConflictingActions(Func<IEnumerable<ApiDescription>, ApiDescription> conflictingActionsResolver)
{
_conflictingActionsResolver = conflictingActionsResolver;
}
public void RootUrl(Func<HttpRequestMessage, string> rootUrlResolver)
{
_rootUrlResolver = rootUrlResolver;
}
public void CustomProvider(Func<ISwaggerProvider, ISwaggerProvider> customProviderFactory)
{
_customProviderFactory = customProviderFactory;
}
/// <summary>
/// 显示开发者信息
/// </summary>
public void ShowDeveloperInfo()
{
ShowDeveloper = true;
}
internal ISwaggerProvider GetSwaggerProvider(HttpRequestMessage swaggerRequest)
{
var httpConfig = swaggerRequest.GetConfiguration();
var securityDefintitions = _securitySchemeBuilders.Any()
? _securitySchemeBuilders.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Build())
: null;
// NOTE: Instantiate & add the XML comments filters here so they're executed before any
// custom filters AND so they can share the same XPathDocument (perf. optimization)
var modelFilters = _modelFilters.Select(factory => factory()).ToList();
var operationFilters = _operationFilters.Select(factory => factory()).ToList();
foreach (var xmlDocFactory in _xmlDocFactories)
{
var xmlDoc = xmlDocFactory();
modelFilters.Insert(0, new ApplyXmlTypeComments(xmlDoc));
operationFilters.Insert(0, new ApplyXmlActionComments(xmlDoc));
}
var options = new SwaggerGeneratorOptions(
versionSupportResolver: _versionSupportResolver,
schemes: _schemes,
securityDefinitions: securityDefintitions,
ignoreObsoleteActions: _ignoreObsoleteActions,
groupingKeySelector: _groupingKeySelector,
groupingKeyComparer: _groupingKeyComparer,
customSchemaMappings: _customSchemaMappings,
schemaFilters: _schemaFilters.Select(factory => factory()).ToList(),
modelFilters: modelFilters,
ignoreObsoleteProperties: _ignoreObsoleteProperties,
schemaIdSelector: _schemaIdSelector,
describeAllEnumsAsStrings: _describeAllEnumsAsStrings,
describeStringEnumsInCamelCase: _describeStringEnumsInCamelCase,
applyFiltersToAllSchemas: _applyFiltersToAllSchemas,
operationFilters: operationFilters,
documentFilters: _documentFilters.Select(factory => factory()).ToList(),
conflictingActionsResolver: _conflictingActionsResolver
);
var defaultProvider = new SwaggerGenerator(
httpConfig.Services.GetApiExplorer(),
httpConfig.SerializerSettingsOrDefault(),
_versionInfoBuilder.Build(),
options);
return (_customProviderFactory != null)
? _customProviderFactory(defaultProvider)
: defaultProvider;
}
internal string GetRootUrl(HttpRequestMessage swaggerRequest)
{
return _rootUrlResolver(swaggerRequest);
}
internal IEnumerable<string> GetApiVersions()
{
return _versionInfoBuilder.Build().Select(entry => entry.Key);
}
internal Formatting GetFormatting()
{
return _prettyPrint ? Formatting.Indented : Formatting.None;
}
public static string DefaultRootUrlResolver(HttpRequestMessage request)
{
var scheme = GetHeaderValue(request, "X-Forwarded-Proto") ?? request.RequestUri.Scheme;
var host = GetHeaderValue(request, "X-Forwarded-Host") ?? request.RequestUri.Host;
var port = GetHeaderValue(request, "X-Forwarded-Port") ?? request.RequestUri.Port.ToString(CultureInfo.InvariantCulture);
var httpConfiguration = request.GetConfiguration();
var virtualPathRoot = httpConfiguration.VirtualPathRoot.TrimEnd('/');
return string.Format("{0}://{1}:{2}{3}", scheme, host, port, virtualPathRoot);
}
private static string GetHeaderValue(HttpRequestMessage request, string headerName)
{
IEnumerable<string> list;
return request.Headers.TryGetValues(headerName, out list) ? list.FirstOrDefault() : null;
}
}
}

View File

@@ -0,0 +1,73 @@
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http;
using System.Net.Http.Formatting;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System.Collections.Generic;
using Swashbuckle.Swagger;
using System.Net;
namespace Swashbuckle.Application
{
public class SwaggerDocsHandler : HttpMessageHandler
{
private readonly SwaggerDocsConfig _config;
public SwaggerDocsHandler(SwaggerDocsConfig config)
{
_config = config;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var swaggerProvider = _config.GetSwaggerProvider(request);
var rootUrl = _config.GetRootUrl(request);
var apiVersion = request.GetRouteData().Values["apiVersion"].ToString();
try
{
var swaggerDoc = swaggerProvider.GetSwagger(rootUrl, apiVersion);
var content = ContentFor(request, swaggerDoc);
return TaskFor(new HttpResponseMessage { Content = content });
}
catch (UnknownApiVersion ex)
{
return TaskFor(request.CreateErrorResponse(HttpStatusCode.NotFound, ex));
}
}
private HttpContent ContentFor(HttpRequestMessage request, SwaggerDocument swaggerDoc)
{
var negotiator = request.GetConfiguration().Services.GetContentNegotiator();
var result = negotiator.Negotiate(typeof(SwaggerDocument), request, GetSupportedSwaggerFormatters());
return new ObjectContent(typeof(SwaggerDocument), swaggerDoc, result.Formatter, result.MediaType);
}
private IEnumerable<MediaTypeFormatter> GetSupportedSwaggerFormatters()
{
var jsonFormatter = new JsonMediaTypeFormatter
{
SerializerSettings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
Formatting = _config.GetFormatting(),
Converters = new[] { new VendorExtensionsConverter() }
}
};
// NOTE: The custom converter would not be neccessary in Newtonsoft.Json >= 5.0.5 as JsonExtensionData
// provides similar functionality. But, need to stick with older version for WebApi 5.0.0 compatibility
return new[] { jsonFormatter };
}
private Task<HttpResponseMessage> TaskFor(HttpResponseMessage response)
{
var tsc = new TaskCompletionSource<HttpResponseMessage>();
tsc.SetResult(response);
return tsc.Task;
}
}
}

View File

@@ -0,0 +1,179 @@
using System;
using System.Linq;
using System.Net.Http;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using Newtonsoft.Json;
using Swashbuckle.SwaggerUi;
namespace Swashbuckle.Application
{
public class SwaggerUiConfig
{
private readonly Dictionary<string, EmbeddedAssetDescriptor> _pathToAssetMap;
private readonly Dictionary<string, string> _templateParams;
private readonly Func<HttpRequestMessage, string> _rootUrlResolver;
public SwaggerUiConfig(IEnumerable<string> discoveryPaths, Func<HttpRequestMessage, string> rootUrlResolver)
{
_pathToAssetMap = new Dictionary<string, EmbeddedAssetDescriptor>();
_templateParams = new Dictionary<string, string>
{
{ "%(StylesheetIncludes)", "" },
{ "%(DiscoveryPaths)", String.Join("|", discoveryPaths) },
{ "%(BooleanValues)", "true|false" },
{ "%(ValidatorUrl)", "" },
{ "%(CustomScripts)", "" },
{ "%(DocExpansion)", "none" },
{ "%(SupportedSubmitMethods)", "get|put|post|delete|options|head|patch" },
{ "%(OAuth2Enabled)", "false" },
{ "%(OAuth2ClientId)", "" },
{ "%(OAuth2ClientSecret)", "" },
{ "%(OAuth2Realm)", "" },
{ "%(OAuth2AppName)", "" },
{ "%(OAuth2ScopeSeperator)", "," },
{ "%(OAuth2AdditionalQueryStringParams)", "{}" },
{ "%(ApiKeyName)", "api_key" },
{ "%(ApiKeyIn)", "query" }
};
_rootUrlResolver = rootUrlResolver;
MapPathsForSwaggerUiAssets();
// Use some custom versions to support config and extensionless paths
var thisAssembly = GetType().Assembly;
CustomAsset("index", thisAssembly, "Swashbuckle.SwaggerUi.CustomAssets.index.html", isTemplate: true);
CustomAsset("css/screen.css", thisAssembly, "Swashbuckle.SwaggerUi.CustomAssets.screen.css");
CustomAsset("css/typography.css", thisAssembly, "Swashbuckle.SwaggerUi.CustomAssets.typography.css");
//CustomAsset("index", thisAssembly, "Swashbuckle.Core.SwaggerUi.Ui.index.html", isTemplate: true);
//CustomAsset("css/screen-css", thisAssembly, "Swashbuckle.Core.SwaggerUi.Ui.screen.css");
//CustomAsset("css/typography-css", thisAssembly, "Swashbuckle.Core.SwaggerUi.Ui.typography.css");
//CustomAsset("css/reset-css", thisAssembly, "Swashbuckle.Core.SwaggerUi.Ui.reset.css");
//CustomAsset("css/ext-css", thisAssembly, "Swashbuckle.Core.SwaggerUi.Ui.ext.css");
}
public void InjectStylesheet(Assembly resourceAssembly, string resourceName, string media = "screen", bool isTemplate = false)
{
var path = "ext/" + resourceName.Replace(".", "-");
var stringBuilder = new StringBuilder(_templateParams["%(StylesheetIncludes)"]);
stringBuilder.AppendLine("<link href='" + path + "' media='" + media + "' rel='stylesheet' type='text/css' />");
_templateParams["%(StylesheetIncludes)"] = stringBuilder.ToString();
CustomAsset(path, resourceAssembly, resourceName, isTemplate);
}
public void BooleanValues(IEnumerable<string> values)
{
_templateParams["%(BooleanValues)"] = String.Join("|", values);
}
public void SetValidatorUrl(string url)
{
_templateParams["%(ValidatorUrl)"] = url;
}
public void DisableValidator()
{
_templateParams["%(ValidatorUrl)"] = "null";
}
public void InjectJavaScript(Assembly resourceAssembly, string resourceName, bool isTemplate = false)
{
var path = "ext/" + resourceName.Replace(".", "-");
var stringBuilder = new StringBuilder(_templateParams["%(CustomScripts)"]);
if (stringBuilder.Length > 0)
stringBuilder.Append("|");
stringBuilder.Append(path);
_templateParams["%(CustomScripts)"] = stringBuilder.ToString();
CustomAsset(path, resourceAssembly, resourceName, isTemplate);
}
public void DocExpansion(DocExpansion docExpansion)
{
_templateParams["%(DocExpansion)"] = docExpansion.ToString().ToLower();
}
public void SupportedSubmitMethods(params string[] methods)
{
_templateParams["%(SupportedSubmitMethods)"] = String.Join("|", methods).ToLower();
}
public void CustomAsset(string path, Assembly resourceAssembly, string resourceName, bool isTemplate = false)
{
if (path == "index") isTemplate = true;
_pathToAssetMap[path] = new EmbeddedAssetDescriptor(resourceAssembly, resourceName, isTemplate);
}
public void EnableDiscoveryUrlSelector()
{
InjectJavaScript(GetType().Assembly, "Swashbuckle.SwaggerUi.CustomAssets.discoveryUrlSelector.js");
}
public void EnableOAuth2Support(string clientId, string realm, string appName)
{
EnableOAuth2Support(clientId, "N/A", realm, appName);
}
public void EnableOAuth2Support(
string clientId,
string clientSecret,
string realm,
string appName,
string scopeSeperator = " ",
Dictionary<string, string> additionalQueryStringParams = null)
{
_templateParams["%(OAuth2Enabled)"] = "true";
_templateParams["%(OAuth2ClientId)"] = clientId;
_templateParams["%(OAuth2ClientSecret)"] = clientSecret;
_templateParams["%(OAuth2Realm)"] = realm;
_templateParams["%(OAuth2AppName)"] = appName;
_templateParams["%(OAuth2ScopeSeperator)"] = scopeSeperator;
if (additionalQueryStringParams != null)
_templateParams["%(OAuth2AdditionalQueryStringParams)"] = JsonConvert.SerializeObject(additionalQueryStringParams);
}
public void EnableApiKeySupport(string name, string apiKeyIn) {
_templateParams["%(ApiKeyName)"] = name;
_templateParams["%(ApiKeyIn)"] = apiKeyIn;
}
internal IAssetProvider GetSwaggerUiProvider()
{
return new EmbeddedAssetProvider(_pathToAssetMap, _templateParams);
}
internal string GetRootUrl(HttpRequestMessage swaggerRequest)
{
return _rootUrlResolver(swaggerRequest);
}
private void MapPathsForSwaggerUiAssets()
{
var thisAssembly = GetType().Assembly;
foreach (var resourceName in thisAssembly.GetManifestResourceNames())
{
if (resourceName.Contains("Swashbuckle.SwaggerUi.CustomAssets")) continue; // original assets only
var path = resourceName
.Replace("\\", "/");
//.Replace(".", "-"); // extensionless to avoid RUMMFAR
_pathToAssetMap[path] = new EmbeddedAssetDescriptor(thisAssembly, resourceName, path == "index");
}
}
}
public enum DocExpansion
{
None,
List,
Full
}
}

View File

@@ -0,0 +1,56 @@
using System.Net.Http;
using System.Threading;
using System;
using System.Threading.Tasks;
using System.Net;
using System.Net.Http.Headers;
using Swashbuckle.SwaggerUi;
namespace Swashbuckle.Application
{
public class SwaggerUiHandler : HttpMessageHandler
{
private readonly SwaggerUiConfig _config;
public SwaggerUiHandler(SwaggerUiConfig config)
{
_config = config;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var swaggerUiProvider = _config.GetSwaggerUiProvider();
var rootUrl = _config.GetRootUrl(request);
var assetPath = request.GetRouteData().Values["assetPath"].ToString();
try
{
var webAsset = swaggerUiProvider.GetAsset(rootUrl, assetPath);
var content = ContentFor(webAsset);
return TaskFor(new HttpResponseMessage { Content = content });
}
catch (AssetNotFound ex)
{
return TaskFor(request.CreateErrorResponse(HttpStatusCode.NotFound, ex));
}
}
private HttpContent ContentFor(Asset webAsset)
{
int bufferSize = webAsset.Stream.Length > int.MaxValue
? int.MaxValue
: (int)webAsset.Stream.Length;
var content = new StreamContent(webAsset.Stream, bufferSize);
content.Headers.ContentType = new MediaTypeHeaderValue(webAsset.MediaType);
return content;
}
private Task<HttpResponseMessage> TaskFor(HttpResponseMessage response)
{
var tsc = new TaskCompletionSource<HttpResponseMessage>();
tsc.SetResult(response);
return tsc.Task;
}
}
}

View File

@@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using Swashbuckle.Swagger;
namespace Swashbuckle.Application
{
public class VendorExtensionsConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType.GetField("vendorExtensions") != null;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var jsonContract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(value.GetType());
writer.WriteStartObject();
foreach (var jsonProp in jsonContract.Properties)
{
var propValue = jsonProp.ValueProvider.GetValue(value);
if (propValue == null && serializer.NullValueHandling == NullValueHandling.Ignore)
continue;
if (jsonProp.PropertyName == "vendorExtensions")
{
var vendorExtensions = (IDictionary<string, object>)propValue;
if (vendorExtensions.Any())
{
foreach (var entry in vendorExtensions)
{
writer.WritePropertyName(entry.Key);
serializer.Serialize(writer, entry.Value);
}
}
}
else
{
writer.WritePropertyName(jsonProp.PropertyName);
serializer.Serialize(writer, propValue);
}
}
writer.WriteEndObject();
}
}
}

View File

@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Swashbuckle.Swagger;
namespace Swashbuckle.Application
{
public class VersionInfoBuilder
{
private readonly Dictionary<string, InfoBuilder> _versionInfos;
public VersionInfoBuilder()
{
_versionInfos = new Dictionary<string, InfoBuilder>();
}
public InfoBuilder Version(string version, string title)
{
var infoBuilder = new InfoBuilder(version, title);
_versionInfos[version] = infoBuilder;
return infoBuilder;
}
public InfoBuilder Version(string version, string title, bool isDefaultRoute)
{
var infoBuilder = new InfoBuilder(version, title, isDefaultRoute);
_versionInfos[version] = infoBuilder;
return infoBuilder;
}
public IDictionary<string, Info> Build()
{
return _versionInfos.ToDictionary(entry => entry.Key, entry => entry.Value.Build());
}
}
}