├── src
└── AzureFunctions.Extensions.Swashbuckle
│ ├── TestFunction
│ ├── host.json
│ ├── server.pfx
│ ├── local.settings.json
│ ├── CustomFilterExample
│ │ └── RemoveSchemasFilter.cs
│ ├── Models
│ │ └── TestModel.cs
│ ├── TestFunction.csproj
│ ├── SwaggerController.cs
│ ├── Program.cs
│ ├── .gitignore
│ ├── TestFunction.xml
│ └── TestController.cs
│ ├── AzureFunctions.Extensions.Swashbuckle
│ ├── Attribute
│ │ ├── SwaggerIgnoreAttribute.cs
│ │ ├── SupportedRequestFormatAttribute.cs
│ │ ├── RequestHttpHeaderAttribute.cs
│ │ ├── SwaggerUploadFileAttribute.cs
│ │ ├── RequestBodyTypeAttribute.cs
│ │ └── QueryStringParameterAttribute.cs
│ ├── EmbededResources
│ │ └── resources.zip
│ ├── SwashBuckle
│ │ ├── SwashBuckleStartupConfig.cs
│ │ ├── Hosting
│ │ │ └── FunctionHostingEnvironment.cs
│ │ ├── Filters
│ │ │ ├── GenerateOperationIdFilter.cs
│ │ │ ├── FunctionsOperationFilter.cs
│ │ │ ├── QueryStringParameterAttributeFilter.cs
│ │ │ ├── XmlCommentsSchemaFilterChanged.cs
│ │ │ ├── Mapper
│ │ │ │ ├── JsonMapper.cs
│ │ │ │ └── TypeMapper.cs
│ │ │ ├── FileUploadOperationFilter.cs
│ │ │ ├── XmlCommentsParameterFilterWithExamples.cs
│ │ │ └── XmlCommentsOperationFilterWithParams.cs
│ │ ├── Extensions
│ │ │ └── AssemblyExtensions.cs
│ │ ├── SwashBuckleClient.cs
│ │ ├── SwashbuckleConfig.cs
│ │ └── Providers
│ │ │ └── FunctionApiDescriptionProvider.cs
│ ├── Settings
│ │ ├── SwaggerDocument.cs
│ │ └── SwaggerDocOptions.cs
│ ├── ISwashBuckleClient.cs
│ ├── SwashBuckleStartupExtension.cs
│ ├── SwashBuckleClientExtension.cs
│ └── AzureFunctions.Extensions.Swashbuckle.csproj
│ ├── AzureFunctions.Extensions.Swashbuckle.sln.DotSettings
│ ├── AzureFunctions.Extensions.Swashbuckle.sln
│ └── .editorconfig
├── stylecop.json
├── LICENSE
├── StyleCop.ruleset
├── .gitignore
└── README.md
/src/AzureFunctions.Extensions.Swashbuckle/TestFunction/host.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0",
3 | "extensions": {
4 | "http": {
5 | "routePrefix": "api"
6 | }
7 | }
8 | }
9 |
10 |
--------------------------------------------------------------------------------
/src/AzureFunctions.Extensions.Swashbuckle/TestFunction/server.pfx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vitalybibikov/AzureExtensions.Swashbuckle/HEAD/src/AzureFunctions.Extensions.Swashbuckle/TestFunction/server.pfx
--------------------------------------------------------------------------------
/src/AzureFunctions.Extensions.Swashbuckle/AzureFunctions.Extensions.Swashbuckle/Attribute/SwaggerIgnoreAttribute.cs:
--------------------------------------------------------------------------------
1 | namespace AzureFunctions.Extensions.Swashbuckle.Attribute
2 | {
3 | public class SwaggerIgnoreAttribute : System.Attribute
4 | {
5 | }
6 | }
--------------------------------------------------------------------------------
/src/AzureFunctions.Extensions.Swashbuckle/AzureFunctions.Extensions.Swashbuckle/EmbededResources/resources.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vitalybibikov/AzureExtensions.Swashbuckle/HEAD/src/AzureFunctions.Extensions.Swashbuckle/AzureFunctions.Extensions.Swashbuckle/EmbededResources/resources.zip
--------------------------------------------------------------------------------
/src/AzureFunctions.Extensions.Swashbuckle/AzureFunctions.Extensions.Swashbuckle/SwashBuckle/SwashBuckleStartupConfig.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 |
3 | namespace AzureFunctions.Extensions.Swashbuckle.SwashBuckle
4 | {
5 | public sealed class SwashBuckleStartupConfig
6 | {
7 | public Assembly Assembly { get; set; }
8 | }
9 | }
--------------------------------------------------------------------------------
/src/AzureFunctions.Extensions.Swashbuckle/TestFunction/local.settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "IsEncrypted": false,
3 | "Values": {
4 | "AzureWebJobsStorage": "UseDevelopmentStorage=true",
5 | "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated"
6 | },
7 |
8 | "SwaggerDocOptions": {
9 | "Title": "Test title with bug",
10 | "XmlPath": "TestFunction.xml",
11 | "PrependOperationWithRoutePrefix": true
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/AzureFunctions.Extensions.Swashbuckle/AzureFunctions.Extensions.Swashbuckle/Settings/SwaggerDocument.cs:
--------------------------------------------------------------------------------
1 | namespace AzureFunctions.Extensions.Swashbuckle.Settings
2 | {
3 | public class SwaggerDocument
4 | {
5 | public string Name { get; set; } = "v1";
6 |
7 | public string Title { get; set; } = "Swashbuckle";
8 |
9 | public string Version { get; set; } = "v1";
10 |
11 | public string Description { get; set; } = "Swagger document by Swashbuckle";
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/AzureFunctions.Extensions.Swashbuckle/AzureFunctions.Extensions.Swashbuckle.sln.DotSettings:
--------------------------------------------------------------------------------
1 |
2 | DO_NOT_SHOW
--------------------------------------------------------------------------------
/src/AzureFunctions.Extensions.Swashbuckle/AzureFunctions.Extensions.Swashbuckle/ISwashBuckleClient.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 |
3 | namespace AzureFunctions.Extensions.Swashbuckle
4 | {
5 | public interface ISwashBuckleClient
6 | {
7 | string RoutePrefix { get; }
8 | Stream GetSwaggerJsonDocument(string host, string documentName = "v1");
9 | Stream GetSwaggerYamlDocument(string host, string documentName = "v1");
10 | Stream GetSwaggerUi(string swaggerUrl);
11 | Stream GetSwaggerOAuth2Redirect();
12 | }
13 | }
--------------------------------------------------------------------------------
/src/AzureFunctions.Extensions.Swashbuckle/AzureFunctions.Extensions.Swashbuckle/Attribute/SupportedRequestFormatAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace AzureFunctions.Extensions.Swashbuckle.Attribute
4 | {
5 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
6 | public class SupportedRequestFormatAttribute : System.Attribute
7 | {
8 | public SupportedRequestFormatAttribute(string mediaType)
9 | {
10 | MediaType = mediaType;
11 | }
12 |
13 | public string MediaType { get; }
14 | }
15 | }
--------------------------------------------------------------------------------
/src/AzureFunctions.Extensions.Swashbuckle/TestFunction/CustomFilterExample/RemoveSchemasFilter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using Microsoft.OpenApi.Models;
4 | using Swashbuckle.AspNetCore.SwaggerGen;
5 |
6 | namespace TestFunction.CustomFilterExample
7 | {
8 | public class RemoveSchemasFilter : IDocumentFilter
9 | {
10 | public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
11 | {
12 | foreach (var item in swaggerDoc.Components.Schemas)
13 | {
14 | swaggerDoc.Components.Schemas.Remove(item.Key);
15 | }
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/AzureFunctions.Extensions.Swashbuckle/AzureFunctions.Extensions.Swashbuckle/Attribute/RequestHttpHeaderAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace AzureFunctions.Extensions.Swashbuckle.Attribute
4 | {
5 | [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)]
6 | public class RequestHttpHeaderAttribute : System.Attribute
7 | {
8 | public RequestHttpHeaderAttribute(string headerName, bool isRequired = false)
9 | {
10 | HeaderName = headerName;
11 | IsRequired = isRequired;
12 | }
13 |
14 | public string HeaderName { get; }
15 |
16 | public bool IsRequired { get; }
17 | }
18 | }
--------------------------------------------------------------------------------
/src/AzureFunctions.Extensions.Swashbuckle/AzureFunctions.Extensions.Swashbuckle/SwashBuckle/Hosting/FunctionHostingEnvironment.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Hosting;
2 | using Microsoft.Extensions.FileProviders;
3 |
4 | namespace AzureFunctions.Extensions.Swashbuckle.SwashBuckle.Hosting
5 | {
6 | internal class FunctionHostingEnvironment : IWebHostEnvironment
7 | {
8 | public string ApplicationName { get; set; }
9 | public IFileProvider ContentRootFileProvider { get; set; }
10 | public string ContentRootPath { get; set; }
11 | public string EnvironmentName { get; set; }
12 | public IFileProvider WebRootFileProvider { get; set; }
13 | public string WebRootPath { get; set; }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/AzureFunctions.Extensions.Swashbuckle/AzureFunctions.Extensions.Swashbuckle/Attribute/SwaggerUploadFileAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace AzureFunctions.Extensions.Swashbuckle.Attribute
4 | {
5 | [AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = true)]
6 | public class SwaggerUploadFileAttribute : System.Attribute
7 | {
8 | public SwaggerUploadFileAttribute(string name, string description, string example = "")
9 | {
10 | this.Name = name;
11 | this.Description = description;
12 | this.Example = example;
13 | }
14 |
15 | public string Name { get; }
16 |
17 | public string Description { get; set; }
18 |
19 | public string Example { get; set; }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/AzureFunctions.Extensions.Swashbuckle/AzureFunctions.Extensions.Swashbuckle/SwashBuckle/Filters/GenerateOperationIdFilter.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Mvc.Controllers;
2 | using Microsoft.OpenApi.Models;
3 | using Swashbuckle.AspNetCore.SwaggerGen;
4 |
5 | namespace AzureFunctions.Extensions.Swashbuckle.SwashBuckle.Filters
6 | {
7 | internal class GenerateOperationIdFilter : IOperationFilter
8 | {
9 | public void Apply(OpenApiOperation operation, OperationFilterContext context)
10 | {
11 | if (context.ApiDescription.ActionDescriptor is ControllerActionDescriptor descriptor
12 | && !string.IsNullOrEmpty(descriptor
13 | .ActionName))
14 | {
15 | operation.OperationId =
16 | descriptor.ActionName;
17 | }
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/src/AzureFunctions.Extensions.Swashbuckle/AzureFunctions.Extensions.Swashbuckle/SwashBuckle/Extensions/AssemblyExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Reflection;
4 |
5 | namespace AzureFunctions.Extensions.Swashbuckle.SwashBuckle.Extensions
6 | {
7 | internal static class AssemblyExtensions
8 | {
9 | public static Stream? GetResourceByName(this Assembly assembly, string name)
10 | {
11 | if (assembly == null)
12 | {
13 | throw new ArgumentNullException(nameof(assembly));
14 | }
15 |
16 | if (assembly == null)
17 | {
18 | throw new ArgumentNullException(nameof(assembly));
19 | }
20 |
21 | return assembly.GetManifestResourceStream($"{typeof(ISwashBuckleClient).Namespace}.{name}");
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/AzureFunctions.Extensions.Swashbuckle/TestFunction/Models/TestModel.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations;
2 |
3 | namespace TestFunction.Models
4 | {
5 | ///
6 | /// Some model
7 | ///
8 | public class TestModel
9 | {
10 | ///
11 | /// Id (example)
12 | ///
13 | /// 3
14 | [Required]
15 | public int Id { get; set; }
16 |
17 | ///
18 | /// Name (example)
19 | ///
20 | /// John
21 | [Required]
22 | [MaxLength(512)]
23 | public string Name { get; set; }
24 |
25 | ///
26 | /// Description (example)
27 | ///
28 | /// Sometimes human
29 | [MaxLength(10240)]
30 | public string Description { get; set; }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/stylecop.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json",
3 | "settings": {
4 | "documentationRules": {
5 | "companyName": "Vitali Bibikov",
6 | "copyrightText": "Copyright (c) {Vitali Bibikov}. All Rights Reserved.\r\n.",
7 | "xmlHeader": false,
8 | "fileNamingConvention": "metadata"
9 | },
10 | "layoutRules": {
11 | "newlineAtEndOfFile": "require"
12 | },
13 | "spacingRules": {},
14 | "readabilityRules": {},
15 | "orderingRules": {
16 | "usingDirectivesPlacement": "outsideNamespace",
17 | "blankLinesBetweenUsingGroups": "omit"
18 | },
19 | "indentation": {
20 | "indentationSize": 4,
21 | "tabSize": 4,
22 | "useTabs": false
23 | },
24 | "maintainabilityRules": {}
25 | }
26 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Vitali Bibikov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/AzureFunctions.Extensions.Swashbuckle/AzureFunctions.Extensions.Swashbuckle/Attribute/RequestBodyTypeAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net.Http;
3 |
4 | namespace AzureFunctions.Extensions.Swashbuckle.Attribute
5 | {
6 | ///
7 | /// Explicit body type definition for functions with as input parameter
8 | ///
9 | [AttributeUsage(AttributeTargets.Parameter)]
10 | public class RequestBodyTypeAttribute : System.Attribute
11 | {
12 | ///
13 | /// Explicit body type definition for functions with as input parameter
14 | ///
15 | /// Body model type
16 | /// Model description
17 | public RequestBodyTypeAttribute(Type bodyType, string description)
18 | {
19 | this.Type = bodyType;
20 | this.Description = description;
21 | }
22 |
23 | ///
24 | /// Gets Body model type
25 | ///
26 | public Type Type { get; }
27 |
28 | ///
29 | /// Gets Body model description
30 | ///
31 | public string Description { get; }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/AzureFunctions.Extensions.Swashbuckle/AzureFunctions.Extensions.Swashbuckle/Settings/SwaggerDocOptions.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using Microsoft.OpenApi;
3 | using Swashbuckle.AspNetCore.SwaggerGen;
4 |
5 | namespace AzureFunctions.Extensions.Swashbuckle.Settings
6 | {
7 | public class SwaggerDocOptions
8 | {
9 | public string? Title { get; set; } = Assembly.GetAssembly(typeof(SwaggerDocOptions))?.GetName().Name;
10 |
11 | public string? XmlPath { get; set; }
12 |
13 | public bool AddCodeParameter { get; set; } = true;
14 |
15 | public IEnumerable Documents { get; set; } = new List();
16 |
17 | public bool PrependOperationWithRoutePrefix { get; set; } = true;
18 |
19 | public Uri OverridenPathToSwaggerJson { get; set; } = default!;
20 |
21 | public OpenApiSpecVersion SpecVersion { get; set; } = OpenApiSpecVersion.OpenApi3_0;
22 |
23 | public Action? ConfigureSwaggerGen { get; set; }
24 |
25 | public string ClientId { get; set; } = string.Empty;
26 |
27 | public string ClientSecret { get; set; } = string.Empty;
28 |
29 | public string OAuth2RedirectPath { get; set; } = string.Empty;
30 |
31 | public bool UseBasicAuthenticationWithAccessCodeGrant { get; set; } = false;
32 |
33 | public bool UsePkceWithAuthorizationCodeGrant { get; set; } = false;
34 |
35 | public bool AddNewtonsoftSupport { get; set; } = false;
36 |
37 | public string RoutePrefix { get; set; } = string.Empty;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/AzureFunctions.Extensions.Swashbuckle/TestFunction/TestFunction.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net8.0
4 | v4
5 | Exe
6 |
7 |
8 |
9 | TestFunction.xml
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | PreserveNewest
31 |
32 |
33 | PreserveNewest
34 | Never
35 |
36 |
37 | PreserveNewest
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/AzureFunctions.Extensions.Swashbuckle/AzureFunctions.Extensions.Swashbuckle/SwashBuckle/SwashBuckleClient.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 |
4 | namespace AzureFunctions.Extensions.Swashbuckle.SwashBuckle
5 | {
6 | public sealed class SwashBuckleClient : ISwashBuckleClient
7 | {
8 | private readonly SwashbuckleConfig _config;
9 |
10 | public SwashBuckleClient(SwashbuckleConfig config)
11 | {
12 | _config = config;
13 | }
14 |
15 | public Stream GetSwaggerJsonDocument(string host, string documentName = "v1")
16 | {
17 | return _config.GetSwaggerJsonDocument(host, documentName);
18 | }
19 |
20 | public Stream GetSwaggerYamlDocument(string host, string documentName = "v1")
21 | {
22 | return _config.GetSwaggerYamlDocument(host, documentName);
23 | }
24 |
25 | public Stream GetSwaggerOAuth2Redirect()
26 | {
27 | var content = _config.GetSwaggerOAuth2RedirectContent();
28 | var memoryStream = new MemoryStream();
29 | var writer = new StreamWriter(memoryStream);
30 | writer.Write(content);
31 | writer.Flush();
32 | memoryStream.Seek(0, SeekOrigin.Begin);
33 | return memoryStream;
34 | }
35 |
36 | public Stream GetSwaggerUi(string swaggerUrl)
37 | {
38 | var memoryStream = new MemoryStream();
39 | var writer = new StreamWriter(memoryStream);
40 |
41 | writer.Write(_config.GetSwaggerUIContent(swaggerUrl));
42 | writer.Flush();
43 |
44 | memoryStream.Position = 0;
45 | return memoryStream;
46 | }
47 |
48 | public string RoutePrefix => _config.RoutePrefix;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/AzureFunctions.Extensions.Swashbuckle/AzureFunctions.Extensions.Swashbuckle.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.9.34607.119
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AzureFunctions.Extensions.Swashbuckle", "AzureFunctions.Extensions.Swashbuckle\AzureFunctions.Extensions.Swashbuckle.csproj", "{1E41B7D0-91CC-4232-88E8-ED5330EDB7BF}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestFunction", "TestFunction\TestFunction.csproj", "{93B84BC0-2017-4BC2-8191-20EDF594E7AC}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Release|Any CPU = Release|Any CPU
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {1E41B7D0-91CC-4232-88E8-ED5330EDB7BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17 | {1E41B7D0-91CC-4232-88E8-ED5330EDB7BF}.Debug|Any CPU.Build.0 = Debug|Any CPU
18 | {1E41B7D0-91CC-4232-88E8-ED5330EDB7BF}.Release|Any CPU.ActiveCfg = Release|Any CPU
19 | {1E41B7D0-91CC-4232-88E8-ED5330EDB7BF}.Release|Any CPU.Build.0 = Release|Any CPU
20 | {93B84BC0-2017-4BC2-8191-20EDF594E7AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {93B84BC0-2017-4BC2-8191-20EDF594E7AC}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {93B84BC0-2017-4BC2-8191-20EDF594E7AC}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {93B84BC0-2017-4BC2-8191-20EDF594E7AC}.Release|Any CPU.Build.0 = Release|Any CPU
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | GlobalSection(ExtensibilityGlobals) = postSolution
29 | SolutionGuid = {34BB311A-992E-44A4-8F07-34B7D23F8139}
30 | EndGlobalSection
31 | EndGlobal
32 |
--------------------------------------------------------------------------------
/src/AzureFunctions.Extensions.Swashbuckle/AzureFunctions.Extensions.Swashbuckle/SwashBuckle/Filters/FunctionsOperationFilter.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using AzureFunctions.Extensions.Swashbuckle.Attribute;
3 | using Microsoft.OpenApi.Models;
4 | using Swashbuckle.AspNetCore.SwaggerGen;
5 |
6 | namespace AzureFunctions.Extensions.Swashbuckle.SwashBuckle.Filters
7 | {
8 | internal class FunctionsOperationFilter : IOperationFilter
9 | {
10 | public void Apply(OpenApiOperation operation, OperationFilterContext context)
11 | {
12 | operation.Parameters ??= new List
13 | {
14 | Capacity = 4
15 | };
16 |
17 | foreach (var customAttribute in context.MethodInfo.GetCustomAttributes(typeof(RequestHttpHeaderAttribute), false))
18 | {
19 | operation.Parameters.Add(new OpenApiParameter
20 | {
21 | Name = (customAttribute as RequestHttpHeaderAttribute)!.HeaderName,
22 | In = ParameterLocation.Header,
23 | Schema = new OpenApiSchema { Type = "string" },
24 | Required = (customAttribute as RequestHttpHeaderAttribute)!.IsRequired
25 | });
26 | }
27 |
28 | foreach (var customAttribute in context.MethodInfo.DeclaringType!.GetCustomAttributes(
29 | typeof(RequestHttpHeaderAttribute), false))
30 | {
31 | operation.Parameters.Add(new OpenApiParameter
32 | {
33 | Name = (customAttribute as RequestHttpHeaderAttribute)!.HeaderName,
34 | In = ParameterLocation.Header,
35 | Schema = new OpenApiSchema { Type = "string" },
36 | Required = (customAttribute as RequestHttpHeaderAttribute)!.IsRequired
37 | });
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/StyleCop.ruleset:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/src/AzureFunctions.Extensions.Swashbuckle/AzureFunctions.Extensions.Swashbuckle/SwashBuckle/Filters/QueryStringParameterAttributeFilter.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using AzureFunctions.Extensions.Swashbuckle.Attribute;
3 | using Microsoft.OpenApi.Any;
4 | using Microsoft.OpenApi.Models;
5 | using Swashbuckle.AspNetCore.SwaggerGen;
6 |
7 | namespace AzureFunctions.Extensions.Swashbuckle.SwashBuckle.Filters
8 | {
9 | internal class QueryStringParameterAttributeFilter : IOperationFilter
10 | {
11 | private readonly ISchemaGenerator schemaGenerator;
12 |
13 | public QueryStringParameterAttributeFilter(ISchemaGenerator schemaGenerator)
14 | {
15 | this.schemaGenerator = schemaGenerator ?? throw new ArgumentNullException(nameof(schemaGenerator));
16 | }
17 |
18 | public void Apply(OpenApiOperation operation, OperationFilterContext context)
19 | {
20 | if (context.MethodInfo.DeclaringType != null)
21 | {
22 | var attributes = context.MethodInfo.DeclaringType.GetCustomAttributes(true)
23 | .Union(context.MethodInfo.GetCustomAttributes(true))
24 | .OfType();
25 |
26 | foreach (var attribute in attributes)
27 | {
28 | var apiParameter = new OpenApiParameter
29 | {
30 | Name = attribute.Name,
31 | Description = attribute.Description,
32 | In = ParameterLocation.Query,
33 | Required = attribute.Required,
34 | Schema = this.schemaGenerator.GenerateSchema(attribute?.DataType, new SchemaRepository())
35 | };
36 |
37 | if (attribute != null)
38 | {
39 | switch (attribute.Example)
40 | {
41 | case OpenApiNull _:
42 | /* ignore */
43 | break;
44 |
45 | default:
46 | // set both examples
47 | apiParameter.Schema.Example = attribute.Example;
48 | apiParameter.Example = apiParameter.Schema.Example;
49 | break;
50 | }
51 | }
52 |
53 | operation.Parameters.Add(apiParameter);
54 | }
55 | }
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/AzureFunctions.Extensions.Swashbuckle/AzureFunctions.Extensions.Swashbuckle/SwashBuckleStartupExtension.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Text.Json;
3 | using System.Text.Json.Serialization.Metadata;
4 | using AzureFunctions.Extensions.Swashbuckle.Settings;
5 | using AzureFunctions.Extensions.Swashbuckle.SwashBuckle;
6 | using AzureFunctions.Extensions.Swashbuckle.SwashBuckle.Providers;
7 | using Microsoft.AspNetCore.Mvc.ApiExplorer;
8 | using Microsoft.AspNetCore.Mvc.Formatters;
9 | using Microsoft.AspNetCore.Mvc.ModelBinding;
10 | using Microsoft.Extensions.DependencyInjection;
11 |
12 | namespace AzureFunctions.Extensions.Swashbuckle
13 | {
14 | public static class SwashBuckleStartupExtension
15 | {
16 | public static IServiceCollection AddSwashBuckle(
17 | this IServiceCollection services,
18 | Action? configureDocOptionsAction = null,
19 | Assembly? executingAssembly = null)
20 | {
21 | if (configureDocOptionsAction != null)
22 | {
23 | services.Configure(configureDocOptionsAction);
24 | }
25 |
26 | Assembly? assembly;
27 |
28 | if (executingAssembly == null)
29 | {
30 | assembly = Assembly.GetEntryAssembly();
31 |
32 | if (assembly == null)
33 | {
34 | throw new ArgumentNullException(nameof(assembly));
35 | }
36 | }
37 | else
38 | {
39 | assembly = executingAssembly;
40 | }
41 |
42 | services.AddSingleton();
43 | services.AddSingleton();
44 | services.AddSingleton(new EmptyModelMetadataProvider());
45 | services.AddSingleton(new SwashBuckleStartupConfig
46 | {
47 | Assembly = assembly
48 | });
49 |
50 | var jsonOptions = new JsonSerializerOptions
51 | {
52 | PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
53 | WriteIndented = true,
54 | TypeInfoResolver = new DefaultJsonTypeInfoResolver()
55 | };
56 |
57 | var formatter = new SystemTextJsonOutputFormatter(jsonOptions);
58 | services.AddSingleton(formatter);
59 | services.AddSingleton();
60 |
61 | return services;
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/AzureFunctions.Extensions.Swashbuckle/AzureFunctions.Extensions.Swashbuckle/SwashBuckle/Filters/XmlCommentsSchemaFilterChanged.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reflection;
3 | using System.Xml.XPath;
4 | using AzureFunctions.Extensions.Swashbuckle.SwashBuckle.Filters.Mapper;
5 | using Microsoft.OpenApi.Models;
6 | using Swashbuckle.AspNetCore.SwaggerGen;
7 |
8 | namespace AzureFunctions.Extensions.Swashbuckle.SwashBuckle.Filters
9 | {
10 | public class XmlCommentsSchemaFilterChanged : ISchemaFilter
11 | {
12 | private readonly XPathNavigator xmlNavigator;
13 |
14 | public XmlCommentsSchemaFilterChanged(XPathDocument xmlDoc)
15 | {
16 | this.xmlNavigator = xmlDoc.CreateNavigator() ?? throw new ArgumentException();
17 | }
18 |
19 | public void Apply(OpenApiSchema schema, SchemaFilterContext context)
20 | {
21 | this.ApplyTypeTags(schema, context.Type);
22 |
23 | if (context.MemberInfo != null && context.ParameterInfo == null)
24 | {
25 | this.ApplyFieldOrPropertyTags(schema, context.MemberInfo);
26 | }
27 | }
28 |
29 | private void ApplyTypeTags(OpenApiSchema schema, Type type)
30 | {
31 | var typeMemberName = XmlCommentsNodeNameHelper.GetMemberNameForType(type);
32 | var typeSummaryNode = this.xmlNavigator.SelectSingleNode($"/doc/members/member[@name='{typeMemberName}']/summary");
33 |
34 | if (typeSummaryNode != null)
35 | {
36 | schema.Description = XmlCommentsTextHelper.Humanize(typeSummaryNode.InnerXml);
37 | }
38 | }
39 |
40 | private void ApplyFieldOrPropertyTags(OpenApiSchema schema, MemberInfo fieldOrPropertyInfo)
41 | {
42 | var fieldOrPropertyMemberName = XmlCommentsNodeNameHelper.GetMemberNameForFieldOrProperty(fieldOrPropertyInfo);
43 | var fieldOrPropertyNode = this.xmlNavigator.SelectSingleNode($"/doc/members/member[@name='{fieldOrPropertyMemberName}']");
44 |
45 | if (fieldOrPropertyNode == null)
46 | {
47 | return;
48 | }
49 |
50 | var summaryNode = fieldOrPropertyNode.SelectSingleNode("summary");
51 | if (summaryNode != null)
52 | {
53 | schema.Description = XmlCommentsTextHelper.Humanize(summaryNode.InnerXml);
54 | }
55 |
56 | var exampleNode = fieldOrPropertyNode.SelectSingleNode("example");
57 | if (exampleNode != null)
58 | {
59 | schema.Example = JsonMapper.CreateFromJson(exampleNode.InnerXml);
60 | }
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/AzureFunctions.Extensions.Swashbuckle/TestFunction/SwaggerController.cs:
--------------------------------------------------------------------------------
1 | using System.Net.Http;
2 | using System.Threading.Tasks;
3 | using AzureFunctions.Extensions.Swashbuckle;
4 | using AzureFunctions.Extensions.Swashbuckle.Attribute;
5 | using Microsoft.Azure.Functions.Worker;
6 | using Microsoft.Azure.Functions.Worker.Http;
7 |
8 | namespace TestFunction
9 | {
10 | public class SwaggerController
11 | {
12 | private readonly ISwashBuckleClient swashBuckleClient;
13 |
14 | public SwaggerController(ISwashBuckleClient swashBuckleClient)
15 | {
16 | this.swashBuckleClient = swashBuckleClient;
17 | }
18 |
19 | [SwaggerIgnore]
20 | [Function("SwaggerJson")]
21 | public async Task SwaggerJson(
22 | [HttpTrigger(AuthorizationLevel.Function, "get", Route = "Swagger/json")]
23 | HttpRequestData req)
24 | {
25 | return await this.swashBuckleClient.CreateSwaggerJsonDocumentResponse(req);
26 | }
27 |
28 | [SwaggerIgnore]
29 | [Function("SwaggerYaml")]
30 | public async Task SwaggerYaml(
31 | [HttpTrigger(AuthorizationLevel.Function, "get", Route = "Swagger/yaml")]
32 | HttpRequestData req)
33 | {
34 | return await this.swashBuckleClient.CreateSwaggerYamlDocumentResponse(req);
35 | }
36 |
37 | [SwaggerIgnore]
38 | [Function("SwaggerUi")]
39 | public async Task SwaggerUi(
40 | [HttpTrigger(AuthorizationLevel.Function, "get", Route = "Swagger/ui")]
41 | HttpRequestData req)
42 | {
43 | return await this.swashBuckleClient.CreateSwaggerUIResponse(req, "swagger/json");
44 | }
45 |
46 | ///
47 | /// This is only needed for OAuth2 client. This redirecting document is normally served
48 | /// as a static content. Functions don't provide this out of the box, so we serve it here.
49 | /// Don't forget to set OAuth2RedirectPath configuration option to reflect this route.
50 | ///
51 | ///
52 | ///
53 | ///
54 | [SwaggerIgnore]
55 | [Function("SwaggerOAuth2Redirect")]
56 | public async Task SwaggerOAuth2Redirect(
57 | [HttpTrigger(AuthorizationLevel.Function, "get", Route = "swagger/oauth2-redirect")]
58 | HttpRequestData req)
59 | {
60 | return await this.swashBuckleClient.CreateSwaggerOAuth2RedirectResponse(req);
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/AzureFunctions.Extensions.Swashbuckle/AzureFunctions.Extensions.Swashbuckle/SwashBuckle/Filters/Mapper/JsonMapper.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json;
2 | using Microsoft.OpenApi.Any;
3 |
4 | namespace AzureFunctions.Extensions.Swashbuckle.SwashBuckle.Filters.Mapper
5 | {
6 | public static class JsonMapper
7 | {
8 | public static IOpenApiAny? CreateFromJson(string str)
9 | {
10 | try
11 | {
12 | var json = JsonSerializer.Serialize(str);
13 | var jsonElement = JsonSerializer.Deserialize(json);
14 |
15 | if (jsonElement.ValueKind == JsonValueKind.True || jsonElement.ValueKind == JsonValueKind.False)
16 | {
17 | return new OpenApiBoolean(jsonElement.GetBoolean());
18 | }
19 |
20 | if (jsonElement.ValueKind == JsonValueKind.Number)
21 | {
22 | if (jsonElement.TryGetInt32(out var intValue))
23 | {
24 | return new OpenApiInteger(intValue);
25 | }
26 |
27 | if (jsonElement.TryGetInt64(out var longValue))
28 | {
29 | return new OpenApiLong(longValue);
30 | }
31 |
32 | if (jsonElement.TryGetSingle(out var floatValue) && !float.IsInfinity(floatValue))
33 | {
34 | return new OpenApiFloat(floatValue);
35 | }
36 |
37 | if (jsonElement.TryGetDouble(out var doubleValue))
38 | {
39 | return new OpenApiDouble(doubleValue);
40 | }
41 | }
42 |
43 | if (jsonElement.ValueKind == JsonValueKind.String)
44 | {
45 | return new OpenApiString(jsonElement.ToString());
46 | }
47 |
48 | if (jsonElement.ValueKind == JsonValueKind.Null)
49 | {
50 | return new OpenApiNull();
51 | }
52 |
53 | if (jsonElement.ValueKind == JsonValueKind.Array)
54 | {
55 | return CreateOpenApiArray(jsonElement.EnumerateArray());
56 | }
57 | }
58 | catch
59 | {
60 | }
61 |
62 | return null;
63 | }
64 |
65 | private static IOpenApiAny CreateOpenApiArray(IEnumerable jsonElements)
66 | {
67 | var openApiArray = new OpenApiArray();
68 |
69 | foreach (var jsonElement in jsonElements)
70 | {
71 | var json = jsonElement.ValueKind == JsonValueKind.String
72 | ? $"\"{jsonElement}\""
73 | : jsonElement.ToString();
74 |
75 | openApiArray.Add(CreateFromJson(json));
76 | }
77 |
78 | return openApiArray;
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/AzureFunctions.Extensions.Swashbuckle/AzureFunctions.Extensions.Swashbuckle/Attribute/QueryStringParameterAttribute.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.OpenApi.Any;
2 | using System;
3 |
4 | namespace AzureFunctions.Extensions.Swashbuckle.Attribute
5 | {
6 | [AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = true)]
7 | public class QueryStringParameterAttribute : System.Attribute
8 | {
9 | public QueryStringParameterAttribute(string name, string description)
10 | {
11 | this.Initialize(name, description);
12 | this.Example = new OpenApiNull();
13 | }
14 |
15 | public QueryStringParameterAttribute(string name, string description, string example)
16 | {
17 | this.Initialize(name, description);
18 | this.DataType = typeof(string);
19 | this.Example = new OpenApiString(example);
20 | }
21 |
22 | public QueryStringParameterAttribute(string name, string description, int example)
23 | {
24 | this.Initialize(name, description);
25 | this.DataType = typeof(int);
26 | this.Example = new OpenApiInteger(example);
27 | }
28 |
29 | public QueryStringParameterAttribute(string name, string description, long example)
30 | {
31 | this.Initialize(name, description);
32 | this.DataType = typeof(long);
33 | this.Example = new OpenApiLong(example);
34 | }
35 |
36 | public QueryStringParameterAttribute(string name, string description, double example)
37 | {
38 | this.Initialize(name, description);
39 | this.Example = new OpenApiDouble(example);
40 | }
41 |
42 | public QueryStringParameterAttribute(string name, string description, float example)
43 | {
44 | this.Initialize(name, description);
45 | this.DataType = typeof(float);
46 | this.Example = new OpenApiFloat(example);
47 | }
48 |
49 | public QueryStringParameterAttribute(string name, string description, byte example)
50 | {
51 | this.Initialize(name, description);
52 | this.DataType = typeof(byte);
53 | this.Example = new OpenApiByte(example);
54 | }
55 |
56 | public QueryStringParameterAttribute(string name, string description, bool example)
57 | {
58 | this.Initialize(name, description);
59 | this.DataType = typeof(bool);
60 | this.Example = new OpenApiBoolean(example);
61 | }
62 |
63 | private void Initialize(string name, string description)
64 | {
65 | this.Name = name;
66 | this.Description = description;
67 | }
68 |
69 | public string Name { get; set; }
70 | public Type DataType { get; set; }
71 | public string Description { get; set; }
72 | public bool Required { get; set; } = false;
73 | public IOpenApiAny Example { get; }
74 | }
75 | }
76 |
77 |
--------------------------------------------------------------------------------
/src/AzureFunctions.Extensions.Swashbuckle/AzureFunctions.Extensions.Swashbuckle/SwashBuckle/Filters/FileUploadOperationFilter.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using AzureFunctions.Extensions.Swashbuckle.Attribute;
4 | using Microsoft.OpenApi.Any;
5 | using Microsoft.OpenApi.Models;
6 | using Swashbuckle.AspNetCore.SwaggerGen;
7 |
8 | namespace AzureFunctions.Extensions.Swashbuckle.SwashBuckle.Filters
9 | {
10 | public class FileUploadOperationFilter : IOperationFilter
11 | {
12 | private const string MimeType = "multipart/form-data";
13 |
14 | public void Apply(OpenApiOperation operation, OperationFilterContext context)
15 | {
16 | if (context.MethodInfo.DeclaringType != null)
17 | {
18 | var uploadFiles = context.MethodInfo.DeclaringType.GetCustomAttributes(true)
19 | .Union(context.MethodInfo.GetCustomAttributes(true))
20 | .OfType();
21 |
22 | var swaggerUploadFiles = uploadFiles.ToList();
23 |
24 | if (!swaggerUploadFiles.Any())
25 | {
26 | return;
27 | }
28 |
29 | var uploadFile = swaggerUploadFiles.First();
30 | operation.RequestBody ??= new OpenApiRequestBody();
31 |
32 | if (!operation.RequestBody.Content.ContainsKey(MimeType))
33 | {
34 | operation.RequestBody.Content[MimeType] = new OpenApiMediaType();
35 | }
36 |
37 | operation.RequestBody.Content[MimeType].Schema ??= new OpenApiSchema();
38 |
39 | var uploadFileName = string.IsNullOrEmpty(uploadFile.Name)
40 | ? "uploadedFile"
41 | : uploadFile.Name;
42 |
43 | var uploadFileDescription = string.IsNullOrEmpty(uploadFile.Description)
44 | ? "File to upload."
45 | : uploadFile.Description;
46 |
47 | var uploadFileMediaType = new OpenApiMediaType
48 | {
49 | Schema = new OpenApiSchema
50 | {
51 | Type = "object",
52 | Properties =
53 | {
54 | [uploadFileName] = new OpenApiSchema
55 | {
56 | Description = uploadFileDescription,
57 | Type = "file",
58 | Format = "binary"
59 | }
60 | },
61 | Required = new HashSet
62 | {
63 | uploadFileName
64 | }
65 | }
66 | };
67 |
68 | operation.RequestBody = new OpenApiRequestBody
69 | {
70 | Content =
71 | {
72 | [MimeType] = uploadFileMediaType
73 | }
74 | };
75 |
76 | if (!string.IsNullOrEmpty(uploadFile.Example))
77 | {
78 | operation.RequestBody.Content[MimeType].Schema.Example =
79 | new OpenApiString(uploadFile.Example);
80 | operation.RequestBody.Content[MimeType].Schema.Description = uploadFile.Example;
81 | }
82 | }
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/AzureFunctions.Extensions.Swashbuckle/AzureFunctions.Extensions.Swashbuckle/SwashBuckle/Filters/XmlCommentsParameterFilterWithExamples.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reflection;
3 | using System.Xml.XPath;
4 | using AzureFunctions.Extensions.Swashbuckle.SwashBuckle.Filters.Mapper;
5 | using Microsoft.OpenApi.Models;
6 | using Swashbuckle.AspNetCore.SwaggerGen;
7 |
8 | namespace AzureFunctions.Extensions.Swashbuckle.SwashBuckle.Filters
9 | {
10 | public class XmlCommentsParameterFilterWithExamples : IParameterFilter
11 | {
12 | private readonly XPathNavigator xmlNavigator;
13 |
14 | public XmlCommentsParameterFilterWithExamples(XPathDocument xmlDoc)
15 | {
16 | this.xmlNavigator = xmlDoc.CreateNavigator() ?? throw new ArgumentException();
17 | }
18 |
19 | public void Apply(OpenApiParameter parameter, ParameterFilterContext context)
20 | {
21 | if (context.PropertyInfo != null)
22 | {
23 | this.ApplyPropertyTags(parameter, context.PropertyInfo);
24 | }
25 | else if (context.ParameterInfo != null)
26 | {
27 | this.ApplyParamTags(parameter, context.ParameterInfo);
28 | }
29 | }
30 |
31 | private void ApplyPropertyTags(OpenApiParameter parameter, PropertyInfo propertyInfo)
32 | {
33 | var propertyMemberName = XmlCommentsNodeNameHelper.GetMemberNameForFieldOrProperty(propertyInfo);
34 | var propertyNode = this.xmlNavigator.SelectSingleNode($"/doc/members/member[@name='{propertyMemberName}']");
35 |
36 | if (propertyNode == null)
37 | {
38 | return;
39 | }
40 |
41 | var summaryNode = propertyNode.SelectSingleNode("summary");
42 | if (summaryNode != null)
43 | {
44 | parameter.Description = XmlCommentsTextHelper.Humanize(summaryNode.InnerXml);
45 | }
46 |
47 | var exampleNode = propertyNode.SelectSingleNode("example");
48 | if (exampleNode != null)
49 | {
50 | parameter.Example = JsonMapper.CreateFromJson(exampleNode.InnerXml);
51 | }
52 | }
53 |
54 | private void ApplyParamTags(OpenApiParameter parameter, ParameterInfo parameterInfo)
55 | {
56 | if (!(parameterInfo.Member is MethodInfo methodInfo))
57 | {
58 | return;
59 | }
60 |
61 | // If method is from a constructed generic type, look for comments from the generic type method
62 | var targetMethod = methodInfo.DeclaringType!.IsConstructedGenericType
63 | ? methodInfo.GetUnderlyingGenericTypeMethod()
64 | : methodInfo;
65 |
66 | if (targetMethod == null)
67 | {
68 | return;
69 | }
70 |
71 | var methodMemberName = XmlCommentsNodeNameHelper.GetMemberNameForMethod(targetMethod);
72 | var paramNode = this.xmlNavigator.SelectSingleNode(
73 | $"/doc/members/member[@name='{methodMemberName}']/param[@name='{parameterInfo.Name}']");
74 |
75 | if (paramNode != null)
76 | {
77 | parameter.Description = XmlCommentsTextHelper.Humanize(paramNode.InnerXml);
78 |
79 | var example = paramNode.GetAttribute("example", string.Empty);
80 | if (!string.IsNullOrEmpty(example))
81 | {
82 | parameter.Example = JsonMapper.CreateFromJson(example);
83 | }
84 | }
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/AzureFunctions.Extensions.Swashbuckle/TestFunction/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using Microsoft.Extensions.Hosting;
4 | using System.Reflection;
5 | using AzureFunctions.Extensions.Swashbuckle.Settings;
6 | using Microsoft.Extensions.DependencyInjection;
7 | using Microsoft.OpenApi;
8 | using Microsoft.OpenApi.Models;
9 | using AzureFunctions.Extensions.Swashbuckle;
10 | using Swashbuckle.AspNetCore.SwaggerGen;
11 |
12 | var host = new HostBuilder()
13 | .ConfigureFunctionsWebApplication(builder =>
14 | {
15 | })
16 | .ConfigureAppConfiguration((hostContext, services) =>
17 | {
18 |
19 | })
20 | .ConfigureServices((hostContext, services) =>
21 | {
22 | //Register the extension
23 | services.AddSwashBuckle(opts =>
24 | {
25 | // If you want to add Newtonsoft support insert next line
26 | // opts.AddNewtonsoftSupport = true;
27 | opts.RoutePrefix = "api";
28 | opts.SpecVersion = OpenApiSpecVersion.OpenApi3_0;
29 | opts.AddCodeParameter = true;
30 | opts.PrependOperationWithRoutePrefix = true;
31 | opts.XmlPath = "TestFunction.xml";
32 | opts.Documents = new[]
33 | {
34 | new SwaggerDocument
35 | {
36 | Name = "v1",
37 | Title = "Swagger document",
38 | Description = "Swagger test document",
39 | Version = "v2"
40 | },
41 | new SwaggerDocument
42 | {
43 | Name = "v2",
44 | Title = "Swagger document 2",
45 | Description = "Swagger test document 2",
46 | Version = "v2"
47 | }
48 | };
49 | opts.Title = "Swagger Test";
50 | //opts.OverridenPathToSwaggerJson = new Uri("http://localhost:7071/api/Swagger/json");
51 | opts.ConfigureSwaggerGen = x =>
52 | {
53 | //custom operation example
54 | x.CustomOperationIds(apiDesc => apiDesc.TryGetMethodInfo(out MethodInfo methodInfo)
55 | ? methodInfo.Name
56 | : new Guid().ToString());
57 |
58 | //custom filter example
59 | //x.DocumentFilter();
60 |
61 | //oauth2
62 | x.AddSecurityDefinition("oauth2",
63 | new OpenApiSecurityScheme
64 | {
65 | Type = SecuritySchemeType.OAuth2,
66 | Flows = new OpenApiOAuthFlows
67 | {
68 | Implicit = new OpenApiOAuthFlow
69 | {
70 | AuthorizationUrl = new Uri("https://your.idserver.net/connect/authorize"),
71 | Scopes = new Dictionary
72 | {
73 | { "api.read", "Access read operations" },
74 | { "api.write", "Access write operations" }
75 | }
76 | }
77 | }
78 | });
79 | };
80 |
81 | // set up your client ID if your API is protected
82 | opts.ClientId = "your.client.id";
83 | opts.ClientSecret = "your.client.secret";
84 | opts.UsePkceWithAuthorizationCodeGrant = true;
85 | opts.OAuth2RedirectPath = "http://localhost:7071/api/swagger/oauth2-redirect";
86 | });
87 | })
88 | .Build();
89 |
90 | host.Run();
91 |
--------------------------------------------------------------------------------
/src/AzureFunctions.Extensions.Swashbuckle/AzureFunctions.Extensions.Swashbuckle/SwashBuckleClientExtension.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Azure.Functions.Worker.Http;
2 | using System.IO;
3 | using System.Net;
4 | using System.Net.Http;
5 | using System.Text;
6 | using System.Threading;
7 | using System.Threading.Tasks;
8 |
9 | namespace AzureFunctions.Extensions.Swashbuckle
10 | {
11 | public static class SwashBuckleClientExtension
12 | {
13 | public static async Task CreateSwaggerJsonDocumentResponse(
14 | this ISwashBuckleClient client,
15 | HttpRequestData requestData,
16 | string documentName = "v1")
17 | {
18 | var host = GetBaseUri(requestData);
19 |
20 | var stream = client.GetSwaggerJsonDocument(host, documentName);
21 | var reader = new StreamReader(stream);
22 | var document = await reader.ReadToEndAsync();
23 |
24 | var response = requestData.CreateResponse(HttpStatusCode.OK);
25 | await response.WriteStringAsync(document, CancellationToken.None, Encoding.UTF8);
26 |
27 | //response.Headers.Add("Content-Type", "application/json; charset=utf-8");
28 | return response;
29 | }
30 |
31 | public static async Task CreateSwaggerYamlDocumentResponse(
32 | this ISwashBuckleClient client,
33 | HttpRequestData requestData,
34 | string documentName = "v1")
35 | {
36 | var host = GetBaseUri(requestData);
37 |
38 | var stream = client.GetSwaggerYamlDocument(host, documentName);
39 | var reader = new StreamReader(stream);
40 |
41 | var response = requestData.CreateResponse(HttpStatusCode.OK);
42 |
43 | var document = await reader.ReadToEndAsync();
44 | await response.WriteStringAsync(document, CancellationToken.None, Encoding.UTF8);
45 | response.Headers.Add("Content-Type", "application/json; charset=utf-8");
46 |
47 | return response;
48 | }
49 |
50 | public static async Task CreateSwaggerUIResponse(
51 | this ISwashBuckleClient client,
52 | HttpRequestData requestData,
53 | string documentRoute)
54 | {
55 | var routePrefix = string.IsNullOrEmpty(client.RoutePrefix)
56 | ? string.Empty
57 | : $"/{client.RoutePrefix}";
58 |
59 | var host = GetBaseUri(requestData);
60 | var stream = client.GetSwaggerUi($"{host}{routePrefix}/{documentRoute}");
61 |
62 | var response = requestData.CreateResponse(HttpStatusCode.OK);
63 | using var reader = new StreamReader(stream);
64 | var document = await reader.ReadToEndAsync();
65 | await response.WriteStringAsync(document, CancellationToken.None, Encoding.UTF8);
66 | //response.Headers.Add("Content-Type", "text/html; charset=utf-8");
67 | return response;
68 | }
69 |
70 | public static async Task CreateSwaggerOAuth2RedirectResponse(
71 | this ISwashBuckleClient client,
72 | HttpRequestData requestData)
73 | {
74 | var stream = client.GetSwaggerOAuth2Redirect();
75 |
76 | var response = requestData.CreateResponse(HttpStatusCode.OK);
77 | using var reader = new StreamReader(stream);
78 | var document = await reader.ReadToEndAsync();
79 | await response.WriteStringAsync(document, CancellationToken.None, Encoding.UTF8);
80 | //response.Headers.Add("Content-Type", "text/html; charset=utf-8");
81 | return response;
82 | }
83 |
84 | private static string GetBaseUri(HttpRequestData requestData)
85 | {
86 | var host = requestData.Url.Host;
87 | var port = requestData.Url.Port; // Get the port
88 | var scheme = requestData.Url.Scheme;
89 |
90 | var hostWithPort = (port == 80 && scheme == "http") || (port == 443 && scheme == "https") ? host : $"{host}:{port}";
91 |
92 | var fullHost = $"{scheme}://{hostWithPort}";
93 | return fullHost;
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/AzureFunctions.Extensions.Swashbuckle/AzureFunctions.Extensions.Swashbuckle/AzureFunctions.Extensions.Swashbuckle.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net8.0
4 | AzureExtensions.Swashbuckle
5 | Vitaly Bibikov
6 | Swagger and Swagger UI in Azure Functions by Swashbuckle
7 | AzureFunctions.Extensions.Swashbuckle
8 | 4.0.4
9 | AzureFunctions.Extensions.Swashbuckle
10 | vitalybibikov
11 | https://github.com/vitalybibikov/azure-functions-extensions-swashbuckle
12 | https://github.com/vitalybibikov/azure-functions-extensions-swashbuckle
13 | GitHub
14 | Swagger Swashbuckle Azure Functions openapi openapi3 isolated worker
15 | false
16 | Open API
17 | true
18 | enable
19 | enable
20 | true
21 | True
22 | $(NoWarn);CS1591
23 |
24 |
25 |
26 | true
27 | snupkg
28 | true
29 | true
30 |
31 |
32 |
33 | true
34 |
35 |
36 |
37 | true
38 | true
39 | ..\..\..\StyleCop.ruleset
40 |
41 |
42 |
43 |
44 | all
45 |
46 |
47 | all
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | all
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | PreserveNewest
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 | all
83 |
84 |
85 |
86 |
87 |
88 |
89 | True
90 |
91 |
92 |
93 |
94 |
95 | LICENSE
96 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/src/AzureFunctions.Extensions.Swashbuckle/AzureFunctions.Extensions.Swashbuckle/SwashBuckle/Filters/Mapper/TypeMapper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using Microsoft.OpenApi.Models;
4 |
5 | namespace AzureFunctions.Extensions.Swashbuckle.SwashBuckle.Filters.Mapper
6 | {
7 | public static class TypeMapper
8 | {
9 | private static readonly Dictionary> SimpleTypeToOpenApiSchema =
10 | new Dictionary>
11 | {
12 | [typeof(bool)] = () => new OpenApiSchema { Type = "boolean" },
13 | [typeof(byte)] = () => new OpenApiSchema { Type = "string", Format = "byte" },
14 | [typeof(int)] = () => new OpenApiSchema { Type = "integer", Format = "int32" },
15 | [typeof(uint)] = () => new OpenApiSchema { Type = "integer", Format = "int32" },
16 | [typeof(ushort)] = () => new OpenApiSchema { Type = "integer", Format = "int32" },
17 | [typeof(long)] = () => new OpenApiSchema { Type = "integer", Format = "int64" },
18 | [typeof(ulong)] = () => new OpenApiSchema { Type = "integer", Format = "int64" },
19 | [typeof(float)] = () => new OpenApiSchema { Type = "number", Format = "float" },
20 | [typeof(double)] = () => new OpenApiSchema { Type = "number", Format = "double" },
21 | [typeof(decimal)] = () => new OpenApiSchema { Type = "number", Format = "double" },
22 | [typeof(DateTime)] = () => new OpenApiSchema { Type = "string", Format = "date-time" },
23 | [typeof(DateTimeOffset)] = () => new OpenApiSchema { Type = "string", Format = "date-time" },
24 | [typeof(Guid)] = () => new OpenApiSchema { Type = "string", Format = "uuid" },
25 | [typeof(char)] = () => new OpenApiSchema { Type = "string" },
26 |
27 | [typeof(bool?)] = () => new OpenApiSchema { Type = "boolean", Nullable = true },
28 | [typeof(byte?)] = () => new OpenApiSchema { Type = "string", Format = "byte", Nullable = true },
29 | [typeof(int?)] = () => new OpenApiSchema { Type = "integer", Format = "int32", Nullable = true },
30 | [typeof(uint?)] = () => new OpenApiSchema { Type = "integer", Format = "int32", Nullable = true },
31 | [typeof(ushort?)] = () => new OpenApiSchema { Type = "integer", Format = "int32", Nullable = true },
32 | [typeof(long?)] = () => new OpenApiSchema { Type = "integer", Format = "int64", Nullable = true },
33 | [typeof(ulong?)] = () => new OpenApiSchema { Type = "integer", Format = "int64", Nullable = true },
34 | [typeof(float?)] = () => new OpenApiSchema { Type = "number", Format = "float", Nullable = true },
35 | [typeof(double?)] = () => new OpenApiSchema { Type = "number", Format = "double", Nullable = true },
36 | [typeof(decimal?)] = () => new OpenApiSchema { Type = "number", Format = "double", Nullable = true },
37 | [typeof(DateTime?)] = () => new OpenApiSchema { Type = "string", Format = "date-time", Nullable = true },
38 | [typeof(DateTimeOffset?)] = () =>
39 | new OpenApiSchema { Type = "string", Format = "date-time", Nullable = true },
40 | [typeof(Guid?)] = () => new OpenApiSchema { Type = "string", Format = "uuid", Nullable = true },
41 | [typeof(char?)] = () => new OpenApiSchema { Type = "string", Nullable = true },
42 |
43 | // Uri is treated as simple string.
44 | [typeof(Uri)] = () => new OpenApiSchema { Type = "string" },
45 |
46 | [typeof(string)] = () => new OpenApiSchema { Type = "string" },
47 |
48 | [typeof(object)] = () => new OpenApiSchema { Type = "object" }
49 | };
50 |
51 | public static OpenApiSchema ToOpenApiSpecType(this Type type)
52 | {
53 | if (type == null)
54 | {
55 | throw new ArgumentNullException(nameof(type));
56 | }
57 |
58 | return SimpleTypeToOpenApiSchema.TryGetValue(type, out var result)
59 | ? result()
60 | : new OpenApiSchema { Type = "string" };
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/AzureFunctions.Extensions.Swashbuckle/AzureFunctions.Extensions.Swashbuckle/SwashBuckle/Filters/XmlCommentsOperationFilterWithParams.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Xml.XPath;
3 | using Microsoft.OpenApi.Models;
4 | using Swashbuckle.AspNetCore.SwaggerGen;
5 |
6 | namespace AzureFunctions.Extensions.Swashbuckle.SwashBuckle.Filters
7 | {
8 | public class XmlCommentsOperationFilterWithParams : IOperationFilter
9 | {
10 | private readonly XPathNavigator xmlNavigator;
11 |
12 | public XmlCommentsOperationFilterWithParams(XPathDocument xmlDoc)
13 | {
14 | this.xmlNavigator = xmlDoc.CreateNavigator() ?? throw new ArgumentException();
15 | }
16 |
17 | public void Apply(OpenApiOperation operation, OperationFilterContext context)
18 | {
19 | if (context.MethodInfo == null)
20 | {
21 | return;
22 | }
23 |
24 | var targetMethod = context.MethodInfo.DeclaringType!.IsConstructedGenericType
25 | ? context.MethodInfo.GetUnderlyingGenericTypeMethod()
26 | : context.MethodInfo;
27 |
28 | if (targetMethod == null)
29 | {
30 | return;
31 | }
32 |
33 | this.ApplyParameters(operation, targetMethod);
34 | this.ApplyControllerTags(operation, targetMethod.DeclaringType!);
35 | this.ApplyMethodTags(operation, targetMethod);
36 | }
37 |
38 | private void ApplyParameters(OpenApiOperation operation, MethodInfo methodInfo)
39 | {
40 | if (methodInfo != null)
41 | {
42 | var methodMemberName = XmlCommentsNodeNameHelper.GetMemberNameForMethod(methodInfo);
43 | foreach (var parameter in methodInfo.GetParameters())
44 | {
45 | if (!string.IsNullOrEmpty(parameter.Name))
46 | {
47 | var paramNode = this.xmlNavigator.SelectSingleNode(
48 | $"/doc/members/member[@name='{methodMemberName}']/param[@name='{parameter.Name}']");
49 |
50 | if (paramNode != null)
51 | {
52 | var humanizedDescription = XmlCommentsTextHelper.Humanize(paramNode.InnerXml);
53 |
54 | var operationParameter = operation.Parameters
55 | .FirstOrDefault(x => x.Name == parameter.Name);
56 |
57 | if (operationParameter != null)
58 | {
59 | operationParameter.Description = humanizedDescription;
60 | }
61 | }
62 | }
63 | }
64 | }
65 | }
66 |
67 | private void ApplyControllerTags(OpenApiOperation operation, Type controllerType)
68 | {
69 | var typeMemberName = XmlCommentsNodeNameHelper.GetMemberNameForType(controllerType);
70 | var responseNodes = this.xmlNavigator.Select($"/doc/members/member[@name='{typeMemberName}']/response");
71 | this.ApplyResponseTags(operation, responseNodes);
72 | }
73 |
74 | private void ApplyMethodTags(OpenApiOperation operation, MethodInfo methodInfo)
75 | {
76 | var methodMemberName = XmlCommentsNodeNameHelper.GetMemberNameForMethod(methodInfo);
77 | var methodNode = this.xmlNavigator.SelectSingleNode($"/doc/members/member[@name='{methodMemberName}']");
78 |
79 | if (methodNode == null)
80 | {
81 | return;
82 | }
83 |
84 | var summaryNode = methodNode.SelectSingleNode("summary");
85 | if (summaryNode != null)
86 | {
87 | operation.Summary = XmlCommentsTextHelper.Humanize(summaryNode.InnerXml);
88 | }
89 |
90 | var remarksNode = methodNode.SelectSingleNode("remarks");
91 | if (remarksNode != null)
92 | {
93 | operation.Description = XmlCommentsTextHelper.Humanize(remarksNode.InnerXml);
94 | }
95 |
96 | var responseNodes = methodNode.Select("response");
97 | this.ApplyResponseTags(operation, responseNodes);
98 | }
99 |
100 | private void ApplyResponseTags(OpenApiOperation operation, XPathNodeIterator responseNodes)
101 | {
102 | while (responseNodes.MoveNext())
103 | {
104 | var code = responseNodes.Current!.GetAttribute("code", string.Empty);
105 | var response = operation.Responses.TryGetValue(code, out var operationResponse)
106 | ? operationResponse
107 | : operation.Responses[code] = new OpenApiResponse();
108 |
109 | response.Description = XmlCommentsTextHelper.Humanize(responseNodes.Current.InnerXml);
110 | }
111 | }
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/AzureFunctions.Extensions.Swashbuckle/TestFunction/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # Azure Functions localsettings file
5 | local.settings.json
6 |
7 | # User-specific files
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Build results
17 | [Dd]ebug/
18 | [Dd]ebugPublic/
19 | [Rr]elease/
20 | [Rr]eleases/
21 | x64/
22 | x86/
23 | bld/
24 | [Bb]in/
25 | [Oo]bj/
26 | [Ll]og/
27 |
28 | # Visual Studio 2015 cache/options directory
29 | .vs/
30 | # Uncomment if you have tasks that create the project's static files in wwwroot
31 | #wwwroot/
32 |
33 | # MSTest test Results
34 | [Tt]est[Rr]esult*/
35 | [Bb]uild[Ll]og.*
36 |
37 | # NUNIT
38 | *.VisualState.xml
39 | TestResult.xml
40 |
41 | # Build Results of an ATL Project
42 | [Dd]ebugPS/
43 | [Rr]eleasePS/
44 | dlldata.c
45 |
46 | # DNX
47 | project.lock.json
48 | project.fragment.lock.json
49 | artifacts/
50 |
51 | *_i.c
52 | *_p.c
53 | *_i.h
54 | *.ilk
55 | *.meta
56 | *.obj
57 | *.pch
58 | *.pdb
59 | *.pgc
60 | *.pgd
61 | *.rsp
62 | *.sbr
63 | *.tlb
64 | *.tli
65 | *.tlh
66 | *.tmp
67 | *.tmp_proj
68 | *.log
69 | *.vspscc
70 | *.vssscc
71 | .builds
72 | *.pidb
73 | *.svclog
74 | *.scc
75 |
76 | # Chutzpah Test files
77 | _Chutzpah*
78 |
79 | # Visual C++ cache files
80 | ipch/
81 | *.aps
82 | *.ncb
83 | *.opendb
84 | *.opensdf
85 | *.sdf
86 | *.cachefile
87 | *.VC.db
88 | *.VC.VC.opendb
89 |
90 | # Visual Studio profiler
91 | *.psess
92 | *.vsp
93 | *.vspx
94 | *.sap
95 |
96 | # TFS 2012 Local Workspace
97 | $tf/
98 |
99 | # Guidance Automation Toolkit
100 | *.gpState
101 |
102 | # ReSharper is a .NET coding add-in
103 | _ReSharper*/
104 | *.[Rr]e[Ss]harper
105 | *.DotSettings.user
106 |
107 | # JustCode is a .NET coding add-in
108 | .JustCode
109 |
110 | # TeamCity is a build add-in
111 | _TeamCity*
112 |
113 | # DotCover is a Code Coverage Tool
114 | *.dotCover
115 |
116 | # NCrunch
117 | _NCrunch_*
118 | .*crunch*.local.xml
119 | nCrunchTemp_*
120 |
121 | # MightyMoose
122 | *.mm.*
123 | AutoTest.Net/
124 |
125 | # Web workbench (sass)
126 | .sass-cache/
127 |
128 | # Installshield output folder
129 | [Ee]xpress/
130 |
131 | # DocProject is a documentation generator add-in
132 | DocProject/buildhelp/
133 | DocProject/Help/*.HxT
134 | DocProject/Help/*.HxC
135 | DocProject/Help/*.hhc
136 | DocProject/Help/*.hhk
137 | DocProject/Help/*.hhp
138 | DocProject/Help/Html2
139 | DocProject/Help/html
140 |
141 | # Click-Once directory
142 | publish/
143 |
144 | # Publish Web Output
145 | *.[Pp]ublish.xml
146 | *.azurePubxml
147 | # TODO: Comment the next line if you want to checkin your web deploy settings
148 | # but database connection strings (with potential passwords) will be unencrypted
149 | #*.pubxml
150 | *.publishproj
151 |
152 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
153 | # checkin your Azure Web App publish settings, but sensitive information contained
154 | # in these scripts will be unencrypted
155 | PublishScripts/
156 |
157 | # NuGet Packages
158 | *.nupkg
159 | # The packages folder can be ignored because of Package Restore
160 | **/packages/*
161 | # except build/, which is used as an MSBuild target.
162 | !**/packages/build/
163 | # Uncomment if necessary however generally it will be regenerated when needed
164 | #!**/packages/repositories.config
165 | # NuGet v3's project.json files produces more ignoreable files
166 | *.nuget.props
167 | *.nuget.targets
168 |
169 | # Microsoft Azure Build Output
170 | csx/
171 | *.build.csdef
172 |
173 | # Microsoft Azure Emulator
174 | ecf/
175 | rcf/
176 |
177 | # Windows Store app package directories and files
178 | AppPackages/
179 | BundleArtifacts/
180 | Package.StoreAssociation.xml
181 | _pkginfo.txt
182 |
183 | # Visual Studio cache files
184 | # files ending in .cache can be ignored
185 | *.[Cc]ache
186 | # but keep track of directories ending in .cache
187 | !*.[Cc]ache/
188 |
189 | # Others
190 | ClientBin/
191 | ~$*
192 | *~
193 | *.dbmdl
194 | *.dbproj.schemaview
195 | *.jfm
196 | *.pfx
197 | *.publishsettings
198 | node_modules/
199 | orleans.codegen.cs
200 |
201 | # Since there are multiple workflows, uncomment next line to ignore bower_components
202 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
203 | #bower_components/
204 |
205 | # RIA/Silverlight projects
206 | Generated_Code/
207 |
208 | # Backup & report files from converting an old project file
209 | # to a newer Visual Studio version. Backup files are not needed,
210 | # because we have git ;-)
211 | _UpgradeReport_Files/
212 | Backup*/
213 | UpgradeLog*.XML
214 | UpgradeLog*.htm
215 |
216 | # SQL Server files
217 | *.mdf
218 | *.ldf
219 |
220 | # Business Intelligence projects
221 | *.rdl.data
222 | *.bim.layout
223 | *.bim_*.settings
224 |
225 | # Microsoft Fakes
226 | FakesAssemblies/
227 |
228 | # GhostDoc plugin setting file
229 | *.GhostDoc.xml
230 |
231 | # Node.js Tools for Visual Studio
232 | .ntvs_analysis.dat
233 |
234 | # Visual Studio 6 build log
235 | *.plg
236 |
237 | # Visual Studio 6 workspace options file
238 | *.opt
239 |
240 | # Visual Studio LightSwitch build output
241 | **/*.HTMLClient/GeneratedArtifacts
242 | **/*.DesktopClient/GeneratedArtifacts
243 | **/*.DesktopClient/ModelManifest.xml
244 | **/*.Server/GeneratedArtifacts
245 | **/*.Server/ModelManifest.xml
246 | _Pvt_Extensions
247 |
248 | # Paket dependency manager
249 | .paket/paket.exe
250 | paket-files/
251 |
252 | # FAKE - F# Make
253 | .fake/
254 |
255 | # JetBrains Rider
256 | .idea/
257 | *.sln.iml
258 |
259 | # CodeRush
260 | .cr/
261 |
262 | # Python Tools for Visual Studio (PTVS)
263 | __pycache__/
264 | *.pyc
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.suo
8 | *.user
9 | *.userosscache
10 | *.sln.docstates
11 |
12 | # User-specific files (MonoDevelop/Xamarin Studio)
13 | *.userprefs
14 |
15 | # Build results
16 | [Dd]ebug/
17 | [Dd]ebugPublic/
18 | [Rr]elease/
19 | [Rr]eleases/
20 | x64/
21 | x86/
22 | bld/
23 | [Bb]in/
24 | [Oo]bj/
25 | [Ll]og/
26 | nupkgs/
27 | # Visual Studio 2015/2017 cache/options directory
28 | .vs/
29 | # Uncomment if you have tasks that create the project's static files in wwwroot
30 | #wwwroot/
31 |
32 | # Visual Studio 2017 auto generated files
33 | Generated\ Files/
34 |
35 | # MSTest test Results
36 | [Tt]est[Rr]esult*/
37 | [Bb]uild[Ll]og.*
38 |
39 | # NUNIT
40 | *.VisualState.xml
41 | TestResult.xml
42 |
43 | # Build Results of an ATL Project
44 | [Dd]ebugPS/
45 | [Rr]eleasePS/
46 | dlldata.c
47 |
48 | # Benchmark Results
49 | BenchmarkDotNet.Artifacts/
50 |
51 | # .NET Core
52 | project.lock.json
53 | project.fragment.lock.json
54 | artifacts/
55 | **/Properties/launchSettings.json
56 |
57 | # StyleCop
58 | StyleCopReport.xml
59 |
60 | # Files built by Visual Studio
61 | *_i.c
62 | *_p.c
63 | *_i.h
64 | *.ilk
65 | *.meta
66 | *.obj
67 | *.iobj
68 | *.pch
69 | *.pdb
70 | *.ipdb
71 | *.pgc
72 | *.pgd
73 | *.rsp
74 | *.sbr
75 | *.tlb
76 | *.tli
77 | *.tlh
78 | *.tmp
79 | *.tmp_proj
80 | *.log
81 | *.vspscc
82 | *.vssscc
83 | .builds
84 | *.pidb
85 | *.svclog
86 | *.scc
87 |
88 | # Chutzpah Test files
89 | _Chutzpah*
90 |
91 | # Visual C++ cache files
92 | ipch/
93 | *.aps
94 | *.ncb
95 | *.opendb
96 | *.opensdf
97 | *.sdf
98 | *.cachefile
99 | *.VC.db
100 | *.VC.VC.opendb
101 |
102 | # Visual Studio profiler
103 | *.psess
104 | *.vsp
105 | *.vspx
106 | *.sap
107 |
108 | # Visual Studio Trace Files
109 | *.e2e
110 |
111 | # TFS 2012 Local Workspace
112 | $tf/
113 |
114 | # Guidance Automation Toolkit
115 | *.gpState
116 |
117 | # ReSharper is a .NET coding add-in
118 | _ReSharper*/
119 | *.[Rr]e[Ss]harper
120 | *.DotSettings.user
121 |
122 | # JustCode is a .NET coding add-in
123 | .JustCode
124 |
125 | # TeamCity is a build add-in
126 | _TeamCity*
127 |
128 | # DotCover is a Code Coverage Tool
129 | *.dotCover
130 |
131 | # AxoCover is a Code Coverage Tool
132 | .axoCover/*
133 | !.axoCover/settings.json
134 |
135 | # Visual Studio code coverage results
136 | *.coverage
137 | *.coveragexml
138 |
139 | # NCrunch
140 | _NCrunch_*
141 | .*crunch*.local.xml
142 | nCrunchTemp_*
143 |
144 | # MightyMoose
145 | *.mm.*
146 | AutoTest.Net/
147 |
148 | # Web workbench (sass)
149 | .sass-cache/
150 |
151 | # Installshield output folder
152 | [Ee]xpress/
153 |
154 | # DocProject is a documentation generator add-in
155 | DocProject/buildhelp/
156 | DocProject/Help/*.HxT
157 | DocProject/Help/*.HxC
158 | DocProject/Help/*.hhc
159 | DocProject/Help/*.hhk
160 | DocProject/Help/*.hhp
161 | DocProject/Help/Html2
162 | DocProject/Help/html
163 |
164 | # Click-Once directory
165 | publish/
166 |
167 | # Publish Web Output
168 | *.[Pp]ublish.xml
169 | *.azurePubxml
170 | # Note: Comment the next line if you want to checkin your web deploy settings,
171 | # but database connection strings (with potential passwords) will be unencrypted
172 | *.pubxml
173 | *.publishproj
174 |
175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
176 | # checkin your Azure Web App publish settings, but sensitive information contained
177 | # in these scripts will be unencrypted
178 | PublishScripts/
179 |
180 | # NuGet Packages
181 | *.nupkg
182 | # The packages folder can be ignored because of Package Restore
183 | **/[Pp]ackages/*
184 | # except build/, which is used as an MSBuild target.
185 | !**/[Pp]ackages/build/
186 | # Uncomment if necessary however generally it will be regenerated when needed
187 | #!**/[Pp]ackages/repositories.config
188 | # NuGet v3's project.json files produces more ignorable files
189 | *.nuget.props
190 | *.nuget.targets
191 |
192 | # Microsoft Azure Build Output
193 | csx/
194 | *.build.csdef
195 |
196 | # Microsoft Azure Emulator
197 | ecf/
198 | rcf/
199 |
200 | # Windows Store app package directories and files
201 | AppPackages/
202 | BundleArtifacts/
203 | Package.StoreAssociation.xml
204 | _pkginfo.txt
205 | *.appx
206 |
207 | # Visual Studio cache files
208 | # files ending in .cache can be ignored
209 | *.[Cc]ache
210 | # but keep track of directories ending in .cache
211 | !*.[Cc]ache/
212 |
213 | # Others
214 | ClientBin/
215 | ~$*
216 | *~
217 | *.dbmdl
218 | *.dbproj.schemaview
219 | *.jfm
220 | *.pfx
221 | *.publishsettings
222 | orleans.codegen.cs
223 |
224 | # Including strong name files can present a security risk
225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
226 | #*.snk
227 |
228 | # Since there are multiple workflows, uncomment next line to ignore bower_components
229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
230 | #bower_components/
231 |
232 | # RIA/Silverlight projects
233 | Generated_Code/
234 |
235 | # Backup & report files from converting an old project file
236 | # to a newer Visual Studio version. Backup files are not needed,
237 | # because we have git ;-)
238 | _UpgradeReport_Files/
239 | Backup*/
240 | UpgradeLog*.XML
241 | UpgradeLog*.htm
242 | ServiceFabricBackup/
243 | *.rptproj.bak
244 |
245 | # SQL Server files
246 | *.mdf
247 | *.ldf
248 | *.ndf
249 |
250 | # Business Intelligence projects
251 | *.rdl.data
252 | *.bim.layout
253 | *.bim_*.settings
254 | *.rptproj.rsuser
255 |
256 | # Microsoft Fakes
257 | FakesAssemblies/
258 |
259 | # GhostDoc plugin setting file
260 | *.GhostDoc.xml
261 |
262 | # Node.js Tools for Visual Studio
263 | .ntvs_analysis.dat
264 | node_modules/
265 |
266 | # Visual Studio 6 build log
267 | *.plg
268 |
269 | # Visual Studio 6 workspace options file
270 | *.opt
271 |
272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
273 | *.vbw
274 |
275 | # Visual Studio LightSwitch build output
276 | **/*.HTMLClient/GeneratedArtifacts
277 | **/*.DesktopClient/GeneratedArtifacts
278 | **/*.DesktopClient/ModelManifest.xml
279 | **/*.Server/GeneratedArtifacts
280 | **/*.Server/ModelManifest.xml
281 | _Pvt_Extensions
282 |
283 | # Paket dependency manager
284 | .paket/paket.exe
285 | paket-files/
286 |
287 | # FAKE - F# Make
288 | .fake/
289 |
290 | # JetBrains Rider
291 | .idea/
292 | *.sln.iml
293 |
--------------------------------------------------------------------------------
/src/AzureFunctions.Extensions.Swashbuckle/TestFunction/TestFunction.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | TestFunction
5 |
6 |
7 |
8 |
9 | Some model
10 |
11 |
12 |
13 |
14 | Id (example)
15 |
16 | 3
17 |
18 |
19 |
20 | Name (example)
21 |
22 | John
23 |
24 |
25 |
26 | Description (example)
27 |
28 | Sometimes human
29 |
30 |
31 |
32 | This is only needed for OAuth2 client. This redirecting document is normally served
33 | as a static content. Functions don't provide this out of the box, so we serve it here.
34 | Don't forget to set OAuth2RedirectPath configuration option to reflect this route.
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | TestGet Function
43 |
44 | some request
45 | some id
46 | Awesomeness!
47 | Product created
48 |
49 |
50 |
51 | TestGet Function
52 |
53 | some request
54 | some id
55 | Awesomeness!
56 | Product created
57 |
58 |
59 |
60 |
61 |
62 |
63 | Extension methods to enable registration of the custom implementation generated for the current worker.
64 |
65 |
66 |
67 |
68 | Configures an optimized function executor to the invocation pipeline.
69 |
70 |
71 |
72 |
73 | Auto startup class to register the custom implementation generated for the current worker.
74 |
75 |
76 |
77 |
78 | Configures the to use the custom implementation generated for the current worker.
79 |
80 | The instance to use for service registration.
81 |
82 |
83 |
84 | Custom implementation that returns function metadata definitions for the current worker."/>
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 | Extension methods to enable registration of the custom implementation generated for the current worker.
93 |
94 |
95 |
96 |
97 | Adds the GeneratedFunctionMetadataProvider to the service collection.
98 | During initialization, the worker will return generated function metadata instead of relying on the Azure Functions host for function indexing.
99 |
100 |
101 |
102 |
103 | Auto startup class to register the custom implementation generated for the current worker.
104 |
105 |
106 |
107 |
108 | Configures the to use the custom implementation generated for the current worker.
109 |
110 | The instance to use for service registration.
111 |
112 |
113 |
114 |
--------------------------------------------------------------------------------
/src/AzureFunctions.Extensions.Swashbuckle/TestFunction/TestController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Net;
6 | using System.Net.Http;
7 | using System.Text.Json;
8 | using System.Threading.Tasks;
9 | using AzureFunctions.Extensions.Swashbuckle.Attribute;
10 | using Microsoft.AspNetCore.Http;
11 | using Microsoft.AspNetCore.Mvc;
12 | using Microsoft.Azure.Functions.Worker;
13 | using TestFunction.Models;
14 |
15 | namespace TestFunction
16 | {
17 | [ApiExplorerSettings(GroupName = "testee")]
18 | public class TestController
19 | {
20 | [ProducesResponseType(typeof(TestModel[]), (int)HttpStatusCode.OK)]
21 | [Function("TestGetWithExamples")]
22 | [QueryStringParameter("colour", "The colour of the bike", "Red", Required = true)]
23 | [QueryStringParameter("wheelsize", "Size of wheel", 26, Required = true)]
24 | [QueryStringParameter("used", "Must be used", false, Required = false)]
25 | public async Task GetExamples([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "getexampletest")]
26 | HttpRequest request)
27 | {
28 | return new OkObjectResult(new[] { new TestModel(), new TestModel() });
29 | }
30 |
31 |
32 | [ProducesResponseType(typeof(TestModel[]), (int)HttpStatusCode.OK)]
33 | [Function("TestGets")]
34 | [QueryStringParameter("expand", "it is expand parameter", DataType = typeof(int), Required = true)]
35 | public async Task Gets([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "test")]
36 | HttpRequest request)
37 | {
38 | return new OkObjectResult(new[] { new TestModel(), new TestModel() });
39 | }
40 |
41 | ///
42 | /// TestGet Function
43 | ///
44 | /// some request
45 | /// some id
46 | /// Awesomeness!
47 | /// Product created
48 | [ProducesResponseType(typeof(TestModel), (int)HttpStatusCode.OK)]
49 | [Function("TestGet")]
50 | [ApiExplorerSettings(GroupName = "v1")]
51 | public Task Get(
52 | int id,
53 | [HttpTrigger(AuthorizationLevel.Function, "get", Route = "v1/test/{id}")]
54 | HttpRequest request)
55 | {
56 | return Task.FromResult(new OkObjectResult(new TestModel()));
57 | }
58 |
59 | ///
60 | /// TestGet Function
61 | ///
62 | /// some request
63 | /// some id
64 | /// Awesomeness!
65 | /// Product created
66 | [ProducesResponseType(typeof(TestModel), (int)HttpStatusCode.OK)]
67 | [Function("TestGetv2")]
68 | [ApiExplorerSettings(GroupName = "v2")]
69 | public Task Get2(
70 | int id,
71 | [HttpTrigger(AuthorizationLevel.Function, "get", Route = "v2/test/{id}")]
72 | HttpRequest request)
73 | {
74 | return Task.FromResult(new OkObjectResult(new TestModel()));
75 | }
76 |
77 | [ProducesResponseType(typeof(TestModel), (int)HttpStatusCode.OK)]
78 | [QueryStringParameter("name", "this is name", DataType = typeof(string), Required = true)]
79 | [Function("TestGetCat")]
80 | public Task GetCat(
81 | [HttpTrigger(AuthorizationLevel.Function, "get", Route = "cat/{id}/{testId?}")]
82 | HttpRequest request, int id, int? testId)
83 | {
84 | return Task.FromResult(new OkObjectResult(new TestModel()));
85 | }
86 |
87 | [ProducesResponseType(typeof(TestModel), (int)HttpStatusCode.OK)]
88 | [QueryStringParameter("pageSorting", "pageSorting", DataType = typeof(IEnumerable), Required = false)]
89 | [Function("TestGetSomethingWithArray")]
90 | public Task GetSomethingWithArray(
91 | [HttpTrigger(AuthorizationLevel.Function, "get", Route = "arraytest")]
92 | HttpRequest request)
93 | {
94 | var items = request.Query["pageSorting"].ToList();
95 | return Task.FromResult(new OkObjectResult(items));
96 | }
97 |
98 | [ProducesResponseType(typeof(TestModel), (int)HttpStatusCode.Created)]
99 | [Function("TestAdd")]
100 | public Task Add([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "test")]
101 | TestModel testModel)
102 | {
103 | return Task.FromResult(new CreatedResult("", testModel));
104 | }
105 |
106 | [ProducesResponseType(typeof(TestModel), (int)HttpStatusCode.Created)]
107 | [Function("TestRequestBodyTypePresented")]
108 | public async Task RequestBodyTypePresented(
109 | [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "testandget/{id}")]
110 | [RequestBodyType(typeof(TestModel), "testmodel")]
111 | HttpRequest httpRequest,
112 | long id)
113 | {
114 | if (httpRequest.Method.Equals("post", StringComparison.OrdinalIgnoreCase))
115 | {
116 | using var reader = new StreamReader(httpRequest.Body);
117 | var json = await reader.ReadToEndAsync();
118 | var testModel = JsonSerializer.Deserialize(json);
119 | return new CreatedResult("", testModel);
120 | }
121 |
122 | return new OkResult();
123 | }
124 |
125 | [ProducesResponseType(typeof(TestModel), (int)HttpStatusCode.Created)]
126 | [RequestHttpHeader("x-ms-session-id", true)]
127 | [Function("TestUpload")]
128 | [SwaggerUploadFile("Pdf", "Pdf upload")]
129 | public async Task TestUpload(
130 | [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "test/upload")] HttpRequest req)
131 | {
132 | var data = await req.ReadFormAsync();
133 |
134 | if (data != null)
135 | {
136 | foreach (var formFile in data.Files)
137 | {
138 | using var reader = new StreamReader(formFile.OpenReadStream());
139 | var fileContent = await reader.ReadToEndAsync();
140 | return new OkObjectResult(fileContent.Length);
141 | }
142 | }
143 |
144 | return new NoContentResult();
145 | }
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # AzureExtensions.Swashbuckle v4.0.0
3 |
4 | ### (Searching for collaborators!)
5 |
6 | OpenAPI 2/3 implementation based on Swashbuckle(Swagger) tooling for API's built with Azure Functions
7 |
8 | This product aims to easily provide Swagger and Swagger UI of APIs created in Azure Functions using isolated worker model
9 |
10 | ------------------------------
11 | 4.0.4
12 | - Updated to latest dependencies
13 |
14 | ------------------------------
15 |
16 | 4.0.3
17 | by @MikeHookTransparity
18 | Added ClientSecret, UseBasicAuthenticationWithAccessCodeGrant
19 |
20 | ------------------------------
21 | 4.0.2
22 | - Updated to latest Swagger 6.5.1
23 | - Fixes & Improvements
24 | - Added symbols package
25 |
26 | ------------------------------
27 | 4.0.1
28 |
29 | - Fixed some minor issues
30 | - Updated to new language features
31 | - Added exception handling and nullability
32 | - Doocumenation fixes
33 |
34 | ------------------------------
35 | 4.0.0-beta
36 | - just remebering what the heck is going om here
37 | - Updated to v4 Functions
38 | - Updated to .NET 8
39 | - Updated to isolated worker model (from now on it is going to be the only one that is supported, as inprocess is going to be deprecated)
40 | - Updated to UI v5.17.3
41 | - Updated to Swagger 5.6.5
42 | - Updated docs
43 | - Considering removing support of NewtonJson
44 |
45 | https://www.nuget.org/packages/AzureExtensions.Swashbuckle/4.0.0-beta
46 | ------------------------------
47 | 3.3.1-beta
48 |
49 | - #64 Support for authorization configuration
50 | - #60 Consolidated extensions and added one to support .net 5
51 | - Updated docs
52 | - Updated js/html/css libs
53 | - Some classed made public to support 3-party IoC.
54 | - Fixed several issues, related to versioning and XML comments.
55 | - Updated to UI v3.37.2
56 | - Updated to Swagger 5.6.3
57 | - Updated documentation
58 | - Ability to create multiple versions of documents, example added.
59 | - Added examples of a custom filter, improved test application
60 |
61 | https://www.nuget.org/packages/AzureExtensions.Swashbuckle/4.0.0-beta
62 |
63 |
64 | ------------------------------
65 | 3.1.6
66 |
67 | https://www.nuget.org/packages/AzureExtensions.Swashbuckle/3.1.6
68 |
69 | Fixed #8, #9
70 |
71 | Updated to UI v3.25.1
72 |
73 | Updated to Swagger 5.4.1
74 |
75 | Fixed base url for Swagger UI
76 |
77 | **Breaking:**
78 |
79 | Option and DocumentOption renamed to SwaggerDocOptions and SwaggerDocument respectivly
80 | and moved to AzureFunctions.Extensions.Swashbuckle.Settings namespace
81 |
82 | **Properties renamed:**
83 |
84 | PrepandOperationWithRoutePrefix => PrependOperationWithRoutePrefix
85 |
86 | AddCodeParamater => AddCodeParameter
87 |
88 | **Properties added:**
89 |
90 | Added ability to configure SwaggerGen via ConfigureSwaggerGen
91 |
92 | Added ability to override default url to Swagger json document (in case of reverse proxy/gateway/ingress) are used.
93 |
94 |
95 | **Size:**
96 |
97 | All the resources are places in zip archive in order to decrease result dll size by 338% (from 1.594kb to 472kb)
98 |
99 |
100 |
101 | ------------------------------
102 | 3.0.0
103 | - Updated to v3 Functions
104 | - Updated to 5.0.0 Swashbuckle.AspNetCore nugets
105 | - Merged PRs to fix issues related to RequestBodyType and Ignore attribute
106 | - application/json is a default media type.
107 |
108 |
109 |
110 | # Sample
111 |
112 | https://github.com/vitalybibikov/azure-functions-extensions-swashbuckle/tree/master/src/AzureFunctions.Extensions.Swashbuckle/TestFunction
113 |
114 | # Update
115 |
116 | Version 3.0.0
117 |
118 |
119 | # Getting Started
120 |
121 | 1. Install the standard Nuget package into your Azure Functions application.
122 |
123 | ```
124 | Package Manager : Install-Package AzureExtensions.Swashbuckle
125 | CLI : dotnet add package AzureExtensions.Swashbuckle
126 | ```
127 |
128 | 2. Add Program.cs class on your Functions project.
129 |
130 | !!! Now you need to specify in option the RoutePrefix.
131 |
132 | opts.RoutePrefix = "api";
133 |
134 |
135 | ```csharp
136 | using System;
137 | using System.Collections.Generic;
138 | using Microsoft.Extensions.Hosting;
139 | using System.Reflection;
140 | using AzureFunctions.Extensions.Swashbuckle.Settings;
141 | using Microsoft.Extensions.DependencyInjection;
142 | using Microsoft.OpenApi;
143 | using Microsoft.OpenApi.Models;
144 | using AzureFunctions.Extensions.Swashbuckle;
145 | using Swashbuckle.AspNetCore.SwaggerGen;
146 |
147 | var host = new HostBuilder()
148 | .ConfigureServices((hostContext, services) =>
149 | {
150 | //Register the extension
151 | services.AddSwashBuckle(opts =>
152 | {
153 | // If you want to add Newtonsoft support insert next line
154 | // opts.AddNewtonsoftSupport = true;
155 | opts.RoutePrefix = "api";
156 | opts.SpecVersion = OpenApiSpecVersion.OpenApi3_0;
157 | opts.AddCodeParameter = true;
158 | opts.PrependOperationWithRoutePrefix = true;
159 | opts.XmlPath = "TestFunction.xml";
160 | opts.Documents = new[]
161 | {
162 | new SwaggerDocument
163 | {
164 | Name = "v1",
165 | Title = "Swagger document",
166 | Description = "Swagger test document",
167 | Version = "v2"
168 | },
169 | new SwaggerDocument
170 | {
171 | Name = "v2",
172 | Title = "Swagger document 2",
173 | Description = "Swagger test document 2",
174 | Version = "v2"
175 | }
176 | };
177 | opts.Title = "Swagger Test";
178 | //opts.OverridenPathToSwaggerJson = new Uri("http://localhost:7071/api/Swagger/json");
179 | opts.ConfigureSwaggerGen = x =>
180 | {
181 | //custom operation example
182 | x.CustomOperationIds(apiDesc => apiDesc.TryGetMethodInfo(out MethodInfo methodInfo)
183 | ? methodInfo.Name
184 | : new Guid().ToString());
185 |
186 | //custom filter example
187 | //x.DocumentFilter();
188 |
189 | //oauth2
190 | x.AddSecurityDefinition("oauth2",
191 | new OpenApiSecurityScheme
192 | {
193 | Type = SecuritySchemeType.OAuth2,
194 | Flows = new OpenApiOAuthFlows
195 | {
196 | Implicit = new OpenApiOAuthFlow
197 | {
198 | AuthorizationUrl = new Uri("https://your.idserver.net/connect/authorize"),
199 | Scopes = new Dictionary
200 | {
201 | { "api.read", "Access read operations" },
202 | { "api.write", "Access write operations" }
203 | }
204 | }
205 | }
206 | });
207 | };
208 |
209 | // set up your client ID if your API is protected
210 | opts.ClientId = "your.client.id";
211 | opts.OAuth2RedirectPath = "http://localhost:7071/api/swagger/oauth2-redirect";
212 | });
213 | })
214 | .Build();
215 |
216 | host.Run();
217 |
218 | ```
219 |
220 | 3. Add swagger and swagger ui endpoint functions on your project.
221 |
222 | ```csharp
223 | public class SwaggerController
224 | {
225 | private readonly ISwashBuckleClient swashBuckleClient;
226 |
227 | public SwaggerController(ISwashBuckleClient swashBuckleClient)
228 | {
229 | this.swashBuckleClient = swashBuckleClient;
230 | }
231 |
232 | [SwaggerIgnore]
233 | [Function("SwaggerJson")]
234 | public async Task SwaggerJson(
235 | [HttpTrigger(AuthorizationLevel.Function, "get", Route = "Swagger/json")]
236 | HttpRequestData req)
237 | {
238 | return await this.swashBuckleClient.CreateSwaggerJsonDocumentResponse(req);
239 | }
240 |
241 | [SwaggerIgnore]
242 | [Function("SwaggerYaml")]
243 | public async Task SwaggerYaml(
244 | [HttpTrigger(AuthorizationLevel.Function, "get", Route = "Swagger/yaml")]
245 | HttpRequestData req)
246 | {
247 | return await this.swashBuckleClient.CreateSwaggerYamlDocumentResponse(req);
248 | }
249 |
250 | [SwaggerIgnore]
251 | [Function("SwaggerUi")]
252 | public async Task SwaggerUi(
253 | [HttpTrigger(AuthorizationLevel.Function, "get", Route = "Swagger/ui")]
254 | HttpRequestData req)
255 | {
256 | return await this.swashBuckleClient.CreateSwaggerUIResponse(req, "swagger/json");
257 | }
258 |
259 | ///
260 | /// This is only needed for OAuth2 client. This redirecting document is normally served
261 | /// as a static content. Functions don't provide this out of the box, so we serve it here.
262 | /// Don't forget to set OAuth2RedirectPath configuration option to reflect this route.
263 | ///
264 | ///
265 | ///
266 | ///
267 | [SwaggerIgnore]
268 | [Function("SwaggerOAuth2Redirect")]
269 | public async Task SwaggerOAuth2Redirect(
270 | [HttpTrigger(AuthorizationLevel.Function, "get", Route = "swagger/oauth2-redirect")]
271 | HttpRequestData req)
272 | {
273 | return await this.swashBuckleClient.CreateSwaggerOAuth2RedirectResponse(req);
274 | }
275 | }
276 | ```
277 |
278 | 4. Open Swagger UI URL in your browser.
279 |
280 | If you does not changed api route prefix. Swagger UI URL is https://hostname/api/swagger/ui .
281 |
282 | ## Options
283 |
284 | ### Include Xml document file
285 |
286 | AzureFunctions.Extensions.Swashbuckle can include xml document file.
287 |
288 | 1. Change your functions project's GenerateDocumentationFile option to enable.
289 |
290 | builder.AddSwashBuckle(Assembly.GetExecutingAssembly(), opts =>
291 | {
292 | opts.XmlPath = "TestFunction.xml";
293 | });
294 |
295 | 2. Add configration setting this extensions on your functions project's local.settings.json
296 |
297 | ```json
298 | "SwaggerDocOptions": {
299 | "XmlPath": "TestFunction.xml"
300 | }
301 | ```
302 |
303 | Alternatively you can add this section to your host.json
304 |
305 | ```json
306 | "extensions": {
307 | "Swashbuckle": {
308 | "XmlPath": "TestFunction.xml"
309 | }
310 | }
311 | ```
312 |
--------------------------------------------------------------------------------
/src/AzureFunctions.Extensions.Swashbuckle/AzureFunctions.Extensions.Swashbuckle/SwashBuckle/SwashbuckleConfig.cs:
--------------------------------------------------------------------------------
1 | using System.IO.Compression;
2 | using System.Reflection;
3 | using System.Xml.XPath;
4 | using AzureFunctions.Extensions.Swashbuckle.Settings;
5 | using AzureFunctions.Extensions.Swashbuckle.SwashBuckle.Extensions;
6 | using AzureFunctions.Extensions.Swashbuckle.SwashBuckle.Filters;
7 | using AzureFunctions.Extensions.Swashbuckle.SwashBuckle.Hosting;
8 | using Microsoft.AspNetCore.Hosting;
9 | using Microsoft.AspNetCore.Mvc.ApiExplorer;
10 | using Microsoft.Extensions.DependencyInjection;
11 | using Microsoft.Extensions.Options;
12 | using Microsoft.OpenApi;
13 | using Microsoft.OpenApi.Extensions;
14 | using Microsoft.OpenApi.Models;
15 | using Swashbuckle.AspNetCore.Swagger;
16 | using Swashbuckle.AspNetCore.SwaggerGen;
17 |
18 | namespace AzureFunctions.Extensions.Swashbuckle.SwashBuckle
19 | {
20 | public sealed class SwashbuckleConfig
21 | {
22 | private const string ZippedResources = "EmbededResources.resources.zip";
23 | private const string IndexHtmlName = "index.html";
24 | private const string SwaggerUiName = "swagger-ui.css";
25 | private const string SwaggerUiJsName = "swagger-ui-bundle.js";
26 | private const string SwaggerUiJsPresetName = "swagger-ui-standalone-preset.js";
27 | private const string SwaggerOAuth2RedirectName = "oauth2-redirect.html";
28 |
29 | private static readonly Lazy IndexHtml = new Lazy(() =>
30 | {
31 | var indexHtml = string.Empty;
32 | var assembly = GetAssembly();
33 |
34 | using var stream = assembly.GetResourceByName(ZippedResources);
35 | if (stream != null)
36 | {
37 | using var archive = new ZipArchive(stream);
38 |
39 | indexHtml = LoadAndUpdateDocument(indexHtml, archive, IndexHtmlName);
40 | indexHtml = LoadAndUpdateDocument(indexHtml, archive, SwaggerUiName, "{style}");
41 | indexHtml = LoadAndUpdateDocument(indexHtml, archive, SwaggerUiJsName, "{bundle.js}");
42 | indexHtml = LoadAndUpdateDocument(indexHtml, archive, SwaggerUiJsPresetName, "{standalone-preset.js}");
43 | }
44 | else
45 | {
46 | throw new ArgumentNullException(nameof(stream), "Embedded data stream is null");
47 | }
48 |
49 | return indexHtml;
50 | });
51 |
52 | private readonly IApiDescriptionGroupCollectionProvider apiDescriptionGroupCollectionProvider;
53 |
54 | private readonly Lazy indexHtmlLazy;
55 | private readonly Lazy oauth2RedirectLazy;
56 | private readonly SwaggerDocOptions swaggerOptions;
57 | private readonly string? xmlPath;
58 | private ServiceProvider? serviceProvider;
59 |
60 | public SwashbuckleConfig(
61 | IApiDescriptionGroupCollectionProvider apiDescriptionGroupCollectionProvider,
62 | IOptions swaggerDocOptions,
63 | SwashBuckleStartupConfig startupConfig)
64 | {
65 | this.apiDescriptionGroupCollectionProvider = apiDescriptionGroupCollectionProvider;
66 | this.swaggerOptions = swaggerDocOptions.Value;
67 |
68 | if (!string.IsNullOrWhiteSpace(this.swaggerOptions.XmlPath))
69 | {
70 | var binPath = Path.GetDirectoryName(startupConfig.Assembly.Location);
71 |
72 | if (string.IsNullOrEmpty(binPath))
73 | {
74 | throw new ArgumentNullException(nameof(binPath), "Not found path to an xml directory");
75 | }
76 |
77 | var binDirectory = Directory.CreateDirectory(binPath);
78 | var xmlBasePath = binDirectory?.Parent?.FullName;
79 |
80 | if (xmlBasePath != null)
81 | {
82 | var path = Path.Combine(xmlBasePath, this.swaggerOptions.XmlPath);
83 |
84 | if (File.Exists(path))
85 | {
86 | this.xmlPath = path;
87 | }
88 | }
89 | }
90 |
91 | this.indexHtmlLazy = new Lazy(
92 | () => IndexHtml.Value.Replace("{title}", this.swaggerOptions.Title));
93 |
94 | this.oauth2RedirectLazy = new Lazy(() =>
95 | {
96 | var assembly = GetAssembly();
97 | using var stream = assembly.GetResourceByName(ZippedResources);
98 | using var archive = new ZipArchive(stream!);
99 | var entry = archive.GetEntry(SwaggerOAuth2RedirectName);
100 | using var entryStream = entry!.Open();
101 | using var reader = new StreamReader(entryStream);
102 | return reader.ReadToEnd();
103 | });
104 |
105 | this.Initialize();
106 | }
107 |
108 | public string RoutePrefix => this.swaggerOptions.RoutePrefix;
109 |
110 | public void Initialize()
111 | {
112 | var services = new ServiceCollection();
113 |
114 | services.AddSingleton(this.apiDescriptionGroupCollectionProvider);
115 |
116 | services.AddSingleton(new FunctionHostingEnvironment());
117 |
118 | if (this.swaggerOptions.AddNewtonsoftSupport)
119 | {
120 | services.AddSwaggerGenNewtonsoftSupport();
121 | }
122 |
123 | services.AddSwaggerGen(options =>
124 | {
125 | if (!this.swaggerOptions.Documents.Any())
126 | {
127 | var defaultDocument = new SwaggerDocument();
128 | AddSwaggerDocument(options, defaultDocument);
129 | }
130 | else
131 | {
132 | foreach (var optionDocument in this.swaggerOptions.Documents)
133 | {
134 | AddSwaggerDocument(options, optionDocument);
135 | }
136 | }
137 |
138 | if (!string.IsNullOrWhiteSpace(this.xmlPath))
139 | {
140 | var xmlDoc = new XPathDocument(this.xmlPath);
141 | options.IncludeXmlComments(this.xmlPath);
142 | options.OperationFilter(xmlDoc);
143 | options.ParameterFilter(xmlDoc);
144 | options.SchemaFilter(xmlDoc);
145 | }
146 |
147 | options.OperationFilter();
148 | options.OperationFilter();
149 | options.OperationFilter();
150 | options.OperationFilter();
151 |
152 | this.swaggerOptions.ConfigureSwaggerGen?.Invoke(options);
153 | });
154 |
155 | this.serviceProvider = services.BuildServiceProvider(true);
156 | }
157 |
158 | public string GetSwaggerOAuth2RedirectContent()
159 | {
160 | return this.oauth2RedirectLazy.Value;
161 | }
162 |
163 | public string GetSwaggerUIContent(string swaggerUrl)
164 | {
165 | if (this.swaggerOptions.OverridenPathToSwaggerJson != null)
166 | {
167 | swaggerUrl = this.swaggerOptions.OverridenPathToSwaggerJson.ToString();
168 | }
169 |
170 | var html = this.indexHtmlLazy.Value;
171 | return html
172 | .Replace("{url}", swaggerUrl)
173 | .Replace("{oauth2RedirectUrl}", this.swaggerOptions.OAuth2RedirectPath)
174 | .Replace("{clientId}", this.swaggerOptions.ClientId)
175 | .Replace("{clientSecret}", this.swaggerOptions.ClientSecret)
176 | .Replace("{useBasicAuthenticationWithAccessCodeGrant}", this.swaggerOptions.UseBasicAuthenticationWithAccessCodeGrant ? "true" : "false")
177 | .Replace("{usePkceWithAuthorizationCodeGrant}", this.swaggerOptions.UsePkceWithAuthorizationCodeGrant ? "true" : "false");
178 | }
179 |
180 | public Stream GetSwaggerJsonDocument(string host, string documentName = "v1")
181 | {
182 | var swaggerProvider = this.serviceProvider!.GetRequiredService();
183 | var document = swaggerProvider.GetSwagger(documentName, host, string.Empty);
184 | return this.SerializeJsonDocument(document);
185 | }
186 |
187 | public Stream GetSwaggerYamlDocument(string host, string documentName = "v1")
188 | {
189 | var swaggerProvider = this.serviceProvider!.GetRequiredService();
190 | var document = swaggerProvider.GetSwagger(documentName, host, string.Empty);
191 | return this.SerializeYamlDocument(document);
192 | }
193 |
194 | private static void AddSwaggerDocument(SwaggerGenOptions options, SwaggerDocument document)
195 | {
196 | options.SwaggerDoc(document.Name, new OpenApiInfo
197 | {
198 | Title = document.Title,
199 | Version = document.Version,
200 | Description = document.Description
201 | });
202 | }
203 | private static Assembly GetAssembly()
204 | {
205 | var assembly = Assembly.GetAssembly(typeof(SwashBuckleClient));
206 |
207 | if (assembly == null)
208 | {
209 | throw new ArgumentNullException(nameof(assembly));
210 | }
211 |
212 | return assembly;
213 | }
214 |
215 | private static string LoadAndUpdateDocument(
216 | string documentHtml,
217 | ZipArchive archive,
218 | string entryName,
219 | string? replacement = null)
220 | {
221 | var entry = archive.GetEntry(entryName);
222 | using var stream = entry!.Open();
223 | using var reader = new StreamReader(stream);
224 | var value = reader.ReadToEnd();
225 |
226 | documentHtml = !string.IsNullOrEmpty(replacement) ?
227 | documentHtml.Replace(replacement, value) :
228 | value;
229 |
230 | return documentHtml;
231 | }
232 |
233 | private MemoryStream SerializeJsonDocument(OpenApiDocument document)
234 | {
235 | var memoryStream = new MemoryStream();
236 | document.SerializeAsJson(
237 | memoryStream,
238 | this.swaggerOptions.SpecVersion == OpenApiSpecVersion.OpenApi2_0 ? OpenApiSpecVersion.OpenApi2_0 : OpenApiSpecVersion.OpenApi3_0);
239 |
240 | memoryStream.Position = 0;
241 | return memoryStream;
242 | }
243 |
244 | private MemoryStream SerializeYamlDocument(OpenApiDocument document)
245 | {
246 | var memoryStream = new MemoryStream();
247 | document.SerializeAsYaml(
248 | memoryStream,
249 | this.swaggerOptions.SpecVersion == OpenApiSpecVersion.OpenApi2_0 ? OpenApiSpecVersion.OpenApi2_0 : OpenApiSpecVersion.OpenApi3_0);
250 |
251 | memoryStream.Position = 0;
252 | return memoryStream;
253 | }
254 | }
255 | }
256 |
--------------------------------------------------------------------------------
/src/AzureFunctions.Extensions.Swashbuckle/AzureFunctions.Extensions.Swashbuckle/SwashBuckle/Providers/FunctionApiDescriptionProvider.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.ObjectModel;
2 | using System.Reflection;
3 | using System.Text.RegularExpressions;
4 | using AzureFunctions.Extensions.Swashbuckle.Attribute;
5 | using AzureFunctions.Extensions.Swashbuckle.Settings;
6 | using Microsoft.AspNetCore.Http;
7 | using Microsoft.AspNetCore.Mvc;
8 | using Microsoft.AspNetCore.Mvc.Abstractions;
9 | using Microsoft.AspNetCore.Mvc.ApiExplorer;
10 | using Microsoft.AspNetCore.Mvc.Controllers;
11 | using Microsoft.AspNetCore.Mvc.Formatters;
12 | using Microsoft.AspNetCore.Mvc.ModelBinding;
13 | using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
14 | using Microsoft.Azure.Functions.Worker;
15 | using Microsoft.Extensions.Logging;
16 | using Microsoft.Extensions.Options;
17 |
18 | namespace AzureFunctions.Extensions.Swashbuckle.SwashBuckle.Providers
19 | {
20 | internal class FunctionApiDescriptionProvider : IApiDescriptionGroupCollectionProvider
21 | {
22 | private readonly ICompositeMetadataDetailsProvider compositeMetadataDetailsProvider;
23 | private readonly IModelMetadataProvider modelMetadataProvider;
24 | private readonly SwaggerDocOptions swaggerDocOptions;
25 | private readonly IOutputFormatter outputFormatter;
26 |
27 | public FunctionApiDescriptionProvider(
28 | IOptions functionsOptions,
29 | SwashBuckleStartupConfig startupConfig,
30 | IModelMetadataProvider modelMetadataProvider,
31 | ICompositeMetadataDetailsProvider compositeMetadataDetailsProvider,
32 | IOutputFormatter outputFormatter)
33 | {
34 | this.swaggerDocOptions = functionsOptions.Value;
35 | this.modelMetadataProvider = modelMetadataProvider;
36 | this.compositeMetadataDetailsProvider = compositeMetadataDetailsProvider;
37 | this.outputFormatter = outputFormatter;
38 |
39 | var apiDescGroups = new Dictionary>();
40 | var methods = startupConfig.Assembly.GetTypes()
41 | .SelectMany(t => t.GetMethods())
42 | .Where(m => m.GetCustomAttributes(typeof(FunctionAttribute), false).Any())
43 | .ToArray();
44 |
45 | foreach (var methodInfo in methods)
46 | {
47 | if (!this.TryGetHttpTrigger(methodInfo, out var triggerAttribute))
48 | {
49 | continue;
50 | }
51 |
52 | var functionAttr = (FunctionAttribute?)methodInfo.GetCustomAttribute(typeof(FunctionAttribute), false);
53 |
54 | if (functionAttr == null && triggerAttribute == null)
55 | {
56 | continue;
57 | }
58 |
59 | var apiExplorerSettingsAttribute =
60 | (ApiExplorerSettingsAttribute?)methodInfo.GetCustomAttribute(typeof(ApiExplorerSettingsAttribute), false)
61 | ?? (ApiExplorerSettingsAttribute?)methodInfo.DeclaringType!.GetCustomAttribute(
62 | typeof(ApiExplorerSettingsAttribute),
63 | false);
64 |
65 | var prefix = string.IsNullOrWhiteSpace(functionsOptions.Value.RoutePrefix)
66 | ? string.Empty
67 | : $"{functionsOptions.Value.RoutePrefix.TrimEnd('/')}/";
68 |
69 | string? route = null;
70 |
71 | if (this.swaggerDocOptions.PrependOperationWithRoutePrefix)
72 | {
73 | var routePart = !string.IsNullOrWhiteSpace(triggerAttribute!.Route)
74 | ? triggerAttribute.Route
75 | : functionAttr!.Name;
76 |
77 | route = $"{prefix}{routePart}";
78 | }
79 | else
80 | {
81 | route = !string.IsNullOrWhiteSpace(triggerAttribute!.Route)
82 | ? triggerAttribute.Route
83 | : functionAttr!.Name;
84 | }
85 |
86 | var routes = new List<(string Route, string RemoveParamName)>();
87 |
88 | var regex = new Regex("/\\{(?\\w+)\\?\\}$");
89 | var match = regex.Match(route!);
90 |
91 | var routeParamRemoveRegex = new Regex(":[a-zA-Z]+(\\(.*\\))?");
92 | route = routeParamRemoveRegex.Replace(route!, string.Empty);
93 |
94 | if (match is { Success: true, Captures.Count: 1 })
95 | {
96 | routes.Add(
97 | (route.Replace(match.Value, string.Empty).Replace("//", "/"), match.Groups["paramName"].ToString()));
98 | routes.Add((route.Replace(match.Value, match.Value.Replace("?", string.Empty)), string.Empty));
99 | }
100 | else
101 | {
102 | routes.Add((route, string.Empty));
103 | }
104 |
105 | var verbs = triggerAttribute.Methods ?? ["get", "post", "delete", "head", "patch", "put", "options"];
106 |
107 | for (var index = 0; index < routes.Count; index++)
108 | {
109 | var (routeString, removeParamName) = routes[index];
110 | var apiName = functionAttr!.Name + (index == 0 ? string.Empty : $"-{index}");
111 | var items = verbs.Select(verb => this.CreateDescription(
112 | methodInfo,
113 | routeString,
114 | index,
115 | functionAttr,
116 | apiExplorerSettingsAttribute!,
117 | verb,
118 | triggerAttribute.AuthLevel,
119 | removeParamName,
120 | verbs.Length > 1)).ToArray();
121 |
122 | var groupName =
123 | (items.FirstOrDefault()?.ActionDescriptor as ControllerActionDescriptor)?.ControllerName ??
124 | apiName;
125 | if (!apiDescGroups.ContainsKey(groupName))
126 | {
127 | apiDescGroups[groupName] = new List();
128 | }
129 |
130 | apiDescGroups[groupName].AddRange(items);
131 | }
132 | }
133 |
134 | this.ApiDescriptionGroups = new ApiDescriptionGroupCollection(
135 | new ReadOnlyCollection(
136 | apiDescGroups.Select(
137 | kv => new ApiDescriptionGroup(kv.Key, kv.Value)).ToList()),
138 | 1);
139 | }
140 |
141 | public ApiDescriptionGroupCollection ApiDescriptionGroups { get; }
142 |
143 | private static void SetupDefaultJsonFormatterIfNone(IList supportedMediaTypes)
144 | {
145 | if (supportedMediaTypes.Count == 0)
146 | {
147 | supportedMediaTypes.Add(new ApiRequestFormat
148 | {
149 | MediaType = "application/json"
150 | });
151 | }
152 | }
153 |
154 | private static Regex GetRoutePathParamRegex(string parameterName)
155 | {
156 | return new Regex("\\{[" + parameterName + "]+[\\?]{0,1}\\}");
157 | }
158 |
159 | private bool TryGetHttpTrigger(MethodInfo methodInfo, out HttpTriggerAttribute? triggerAttribute)
160 | {
161 | triggerAttribute = null;
162 | var ignore = methodInfo.GetCustomAttributes().Any(x => x is SwaggerIgnoreAttribute);
163 | if (ignore)
164 | {
165 | return false;
166 | }
167 |
168 | triggerAttribute = this.FindHttpTriggerAttribute(methodInfo);
169 |
170 | if (triggerAttribute == null)
171 | {
172 | return false;
173 | }
174 |
175 | return true;
176 | }
177 |
178 | private ApiDescription CreateDescription(
179 | MethodInfo methodInfo,
180 | string route,
181 | int routeIndex,
182 | FunctionAttribute functionAttr,
183 | ApiExplorerSettingsAttribute apiExplorerSettingsAttr,
184 | string verb,
185 | AuthorizationLevel authorizationLevel,
186 | string? removeParamName = null,
187 | bool manyVerbs = false)
188 | {
189 | string controllerName;
190 | if (apiExplorerSettingsAttr?.GroupName != null)
191 | {
192 | controllerName = apiExplorerSettingsAttr.GroupName;
193 | }
194 | else
195 | {
196 | controllerName = methodInfo.DeclaringType!.Name.EndsWith("Controller")
197 | ? methodInfo.DeclaringType.Name.Remove(
198 | methodInfo.DeclaringType.Name.Length - "Controller".Length, "Controller".Length)
199 | : functionAttr.Name;
200 | }
201 |
202 | var actionName = functionAttr.Name;
203 | var actionNamePrefix = (routeIndex > 0 ? $"_{routeIndex}" : string.Empty) + (manyVerbs ? $"_{verb.ToLower()}" : string.Empty);
204 |
205 | var description = new ApiDescription
206 | {
207 | ActionDescriptor = new ControllerActionDescriptor
208 | {
209 | MethodInfo = methodInfo,
210 | ControllerName = controllerName,
211 | DisplayName = actionName,
212 | ControllerTypeInfo = methodInfo.DeclaringType!.GetTypeInfo(),
213 | Parameters = new List(),
214 | RouteValues = new Dictionary
215 | {
216 | { "controller", controllerName },
217 | { "action", actionName }
218 | },
219 | ActionName = !string.IsNullOrEmpty(actionNamePrefix) ? actionName + actionNamePrefix : string.Empty
220 | },
221 | RelativePath = route,
222 | HttpMethod = verb.ToUpper()
223 | };
224 |
225 | var supportedMediaTypes = methodInfo.GetCustomAttributes()
226 | .Select(x => new ApiRequestFormat { MediaType = x.MediaType })
227 | .ToList();
228 |
229 | SetupDefaultJsonFormatterIfNone(supportedMediaTypes);
230 |
231 | foreach (var supportedMediaType in supportedMediaTypes)
232 | {
233 | description.SupportedRequestFormats.Add(supportedMediaType);
234 | }
235 |
236 | var parameters = this.GetParametersDescription(methodInfo, route).ToList();
237 |
238 | foreach (var parameter in parameters.Where(parameter => parameter.Name != removeParamName))
239 | {
240 | description.ActionDescriptor.Parameters.Add(new ParameterDescriptor
241 | {
242 | Name = parameter.Name,
243 | ParameterType = parameter.Type
244 | });
245 | description.ParameterDescriptions.Add(parameter);
246 | }
247 |
248 | foreach (var apiResponseType in this.GetResponseTypes(methodInfo))
249 | {
250 | description.SupportedResponseTypes.Add(apiResponseType);
251 | }
252 |
253 | if (this.swaggerDocOptions.AddCodeParameter && authorizationLevel != AuthorizationLevel.Anonymous)
254 | {
255 | description.ParameterDescriptions.Add(new ApiParameterDescription
256 | {
257 | Name = "code",
258 | Type = typeof(string),
259 | Source = BindingSource.Query,
260 | RouteInfo = new ApiParameterRouteInfo
261 | {
262 | IsOptional = true
263 | }
264 | });
265 | }
266 |
267 | return description;
268 | }
269 |
270 | private IEnumerable GetResponseTypes(MethodInfo methodInfo)
271 | {
272 | return methodInfo.GetCustomAttributes(typeof(ProducesResponseTypeAttribute))
273 | .Select(customAttribute => customAttribute as ProducesResponseTypeAttribute)
274 | .Select(responseType =>
275 | {
276 | var isVoidResponseType = responseType!.Type == typeof(void);
277 |
278 | return new ApiResponseType
279 | {
280 | ApiResponseFormats = new[]
281 | {
282 | new ApiResponseFormat
283 | {
284 | Formatter = this.outputFormatter,
285 | MediaType = "application/json"
286 | }
287 | },
288 | ModelMetadata = isVoidResponseType ? null : this.modelMetadataProvider.GetMetadataForType(responseType.Type),
289 | Type = isVoidResponseType ? null : responseType.Type,
290 | StatusCode = responseType.StatusCode
291 | };
292 | });
293 | }
294 |
295 | private HttpTriggerAttribute? FindHttpTriggerAttribute(MethodInfo methodInfo)
296 | {
297 | HttpTriggerAttribute? triggerAttribute = null;
298 |
299 | foreach (var parameter in methodInfo.GetParameters())
300 | {
301 | triggerAttribute = parameter.GetCustomAttributes(typeof(HttpTriggerAttribute), false)
302 | .FirstOrDefault() as HttpTriggerAttribute;
303 | if (triggerAttribute != null)
304 | {
305 | break;
306 | }
307 | }
308 |
309 | return triggerAttribute;
310 | }
311 |
312 | private IEnumerable GetParametersDescription(MethodInfo methodInfo, string route)
313 | {
314 | foreach (var parameter in methodInfo.GetParameters())
315 | {
316 | var requestBodyTypeAttribute =
317 | parameter.GetCustomAttribute(typeof(RequestBodyTypeAttribute)) as RequestBodyTypeAttribute;
318 |
319 | if ((parameter.ParameterType == typeof(HttpRequestMessage) ||
320 | parameter.ParameterType == typeof(HttpRequest))
321 | && requestBodyTypeAttribute == null)
322 | {
323 | continue;
324 | }
325 |
326 | if (this.IgnoreParameter(parameter))
327 | {
328 | continue;
329 | }
330 |
331 | var hasHttpTriggerAttribute = parameter.GetCustomAttributes()
332 | .Any(attr => attr is HttpTriggerAttribute);
333 |
334 | var hasFromUriAttribute = false;
335 |
336 | var type = hasHttpTriggerAttribute && requestBodyTypeAttribute != null
337 | ? requestBodyTypeAttribute.Type
338 | : parameter.ParameterType;
339 |
340 | var regex = GetRoutePathParamRegex(parameter.Name!);
341 | var match = regex.Match(route);
342 | var bindingSource = match.Success ? BindingSource.Path
343 | : hasFromUriAttribute ? BindingSource.Query
344 | : BindingSource.Body;
345 |
346 | var optional = bindingSource == BindingSource.Query || match.Value.Contains("?");
347 |
348 | yield return new ApiParameterDescription
349 | {
350 | Name = parameter.Name!,
351 | Type = type,
352 | Source = bindingSource,
353 | RouteInfo = new ApiParameterRouteInfo
354 | {
355 | IsOptional = optional
356 | },
357 | ModelMetadata = new DefaultModelMetadata(
358 | this.modelMetadataProvider,
359 | this.compositeMetadataDetailsProvider,
360 | new DefaultMetadataDetails(
361 | ModelMetadataIdentity.ForType(type),
362 | ModelAttributes.GetAttributesForType(type)))
363 | };
364 | }
365 | }
366 |
367 | private bool IgnoreParameter(ParameterInfo parameter)
368 | {
369 | var ignoreParameterAttribute = parameter.GetCustomAttribute(typeof(SwaggerIgnoreAttribute));
370 | if (ignoreParameterAttribute != null)
371 | {
372 | return true;
373 | }
374 |
375 | if (parameter.ParameterType.Name == "TraceWriter")
376 | {
377 | return true;
378 | }
379 |
380 | if (parameter.ParameterType == typeof(ExecutionContext))
381 | {
382 | return true;
383 | }
384 |
385 | if (parameter.ParameterType == typeof(ILogger))
386 | {
387 | return true;
388 | }
389 |
390 | if (parameter.ParameterType.IsAssignableFrom(typeof(ILogger)))
391 | {
392 | return true;
393 | }
394 |
395 | if (parameter.ParameterType.IsAssignableFrom(typeof(ISwashBuckleClient)))
396 | {
397 | return true;
398 | }
399 |
400 | if (parameter.GetCustomAttributes().Any(attr => attr is HttpTriggerAttribute)
401 | && parameter.GetCustomAttributes().All(attr => !(attr is RequestBodyTypeAttribute)))
402 | {
403 | return true;
404 | }
405 |
406 | return false;
407 | }
408 | }
409 | }
410 |
--------------------------------------------------------------------------------
/src/AzureFunctions.Extensions.Swashbuckle/.editorconfig:
--------------------------------------------------------------------------------
1 | # Version: 1.3.1 (Using https://semver.org/)
2 | # Updated: 2019-08-04
3 | # See https://github.com/RehanSaeed/EditorConfig/releases for release notes.
4 | # See https://github.com/RehanSaeed/EditorConfig for updates to this file.
5 | # See http://EditorConfig.org for more information about .editorconfig files.
6 |
7 | ##########################################
8 | # Common Settings
9 | ##########################################
10 |
11 | # This file is the top-most EditorConfig file
12 | root = true
13 |
14 | # All Files
15 | [*]
16 | charset = utf-8
17 | indent_style = space
18 | indent_size = 4
19 | insert_final_newline = true
20 | trim_trailing_whitespace = true
21 |
22 | ##########################################
23 | # File Extension Settings
24 | ##########################################
25 |
26 | # Visual Studio Solution Files
27 | [*.sln]
28 | indent_style = tab
29 |
30 | # Visual Studio XML Project Files
31 | [*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}]
32 | indent_size = 2
33 |
34 | # Various XML Configuration Files
35 | [*.{xml,config,props,targets,nuspec,resx,ruleset,vsixmanifest,vsct}]
36 | indent_size = 2
37 |
38 | # JSON Files
39 | [*.{json,json5}]
40 | indent_size = 2
41 |
42 | # YAML Files
43 | [*.{yml,yaml}]
44 | indent_size = 2
45 |
46 | # Markdown Files
47 | [*.md]
48 | trim_trailing_whitespace = false
49 |
50 | # Web Files
51 | [*.{htm,html,js,ts,tsx,css,sass,scss,less,svg,vue}]
52 | indent_size = 2
53 |
54 | # Batch Files
55 | [*.{cmd,bat}]
56 |
57 | # Bash Files
58 | [*.sh]
59 | end_of_line = lf
60 |
61 | ##########################################
62 | # .NET Language Conventions
63 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions
64 | ##########################################
65 |
66 | # .NET Code Style Settings
67 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#net-code-style-settings
68 | [*.{cs,csx,cake,vb}]
69 | # "this." and "Me." qualifiers
70 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#this-and-me
71 | dotnet_style_qualification_for_field = true:warning
72 | dotnet_style_qualification_for_property = true:warning
73 | dotnet_style_qualification_for_method = true:warning
74 | dotnet_style_qualification_for_event = true:warning
75 | # Language keywords instead of framework type names for type references
76 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#language-keywords
77 | dotnet_style_predefined_type_for_locals_parameters_members = true:warning
78 | dotnet_style_predefined_type_for_member_access = true:warning
79 | # Modifier preferences
80 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#normalize-modifiers
81 | dotnet_style_require_accessibility_modifiers = always:warning
82 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async
83 | visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async
84 | dotnet_style_readonly_field = true:warning
85 | # Parentheses preferences
86 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#parentheses-preferences
87 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:warning
88 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:warning
89 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:warning
90 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary:suggestion
91 | # Expression-level preferences
92 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#expression-level-preferences
93 | dotnet_style_object_initializer = true:warning
94 | dotnet_style_collection_initializer = true:warning
95 | dotnet_style_explicit_tuple_names = true:warning
96 | dotnet_style_prefer_inferred_tuple_names = true:warning
97 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning
98 | dotnet_style_prefer_auto_properties = true:warning
99 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning
100 | dotnet_style_prefer_conditional_expression_over_assignment = false:suggestion
101 | dotnet_style_prefer_conditional_expression_over_return = false:suggestion
102 | dotnet_style_prefer_compound_assignment = true:warning
103 | # Null-checking preferences
104 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#null-checking-preferences
105 | dotnet_style_coalesce_expression = true:warning
106 | dotnet_style_null_propagation = true:warning
107 | # Parameter preferences
108 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#parameter-preferences
109 | dotnet_code_quality_unused_parameters = all:warning
110 | # More style options (Undocumented)
111 | # https://github.com/MicrosoftDocs/visualstudio-docs/issues/3641
112 | dotnet_style_operator_placement_when_wrapping = end_of_line
113 |
114 | # C# Code Style Settings
115 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#c-code-style-settings
116 | [*.{cs,csx,cake}]
117 | # Implicit and explicit types
118 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#implicit-and-explicit-types
119 | csharp_style_var_for_built_in_types = true:warning
120 | csharp_style_var_when_type_is_apparent = true:warning
121 | csharp_style_var_elsewhere = true:warning
122 | # Expression-bodied members
123 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#expression-bodied-members
124 | # IDE0022
125 | csharp_style_expression_bodied_methods = false:silent
126 | # IDE0021
127 | csharp_style_expression_bodied_constructors = false:silent
128 | # IDE0023, IDE0024
129 | csharp_style_expression_bodied_operators = false:silent
130 | # IDE0025
131 | csharp_style_expression_bodied_properties = true:silent
132 | # IDE0026
133 | csharp_style_expression_bodied_indexers = true:silent
134 | # IDE0027
135 | csharp_style_expression_bodied_accessors = true:silent
136 | # IDE0053
137 | csharp_style_expression_bodied_lambdas = true:silent
138 | # IDE0061
139 | csharp_style_expression_bodied_local_functions = false:silent
140 | # Pattern matching
141 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#pattern-matching
142 | csharp_style_pattern_matching_over_is_with_cast_check = true:warning
143 | csharp_style_pattern_matching_over_as_with_null_check = true:warning
144 | # Inlined variable declarations
145 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#inlined-variable-declarations
146 | csharp_style_inlined_variable_declaration = true:warning
147 | # Expression-level preferences
148 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#expression-level-preferences
149 | csharp_prefer_simple_default_expression = true:warning
150 | # "Null" checking preferences
151 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#c-null-checking-preferences
152 | csharp_style_throw_expression = true:warning
153 | csharp_style_conditional_delegate_call = true:warning
154 | # Code block preferences
155 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#code-block-preferences
156 | csharp_prefer_braces = true:warning
157 | # Unused value preferences
158 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#unused-value-preferences
159 | csharp_style_unused_value_expression_statement_preference = discard_variable:suggestion
160 | csharp_style_unused_value_assignment_preference = discard_variable:suggestion
161 | # Index and range preferences
162 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#index-and-range-preferences
163 | csharp_style_prefer_index_operator = true:warning
164 | csharp_style_prefer_range_operator = true:warning
165 | # Miscellaneous preferences
166 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#miscellaneous-preferences
167 | csharp_style_deconstructed_variable_declaration = true:warning
168 | csharp_style_pattern_local_over_anonymous_function = true:warning
169 | csharp_using_directive_placement = outside_namespace:warning
170 | csharp_prefer_static_local_function = true:warning
171 | csharp_prefer_simple_using_statement = false:warning
172 |
173 | ##########################################
174 | # .NET Formatting Conventions
175 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-code-style-settings-reference#formatting-conventions
176 | ##########################################
177 |
178 | # Organize usings
179 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#organize-using-directives
180 | dotnet_sort_system_directives_first = always:error
181 | dotnet_separate_import_directive_groups = always:error
182 | # Newline options
183 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#new-line-options
184 | csharp_new_line_before_open_brace = all
185 | csharp_new_line_before_else = true
186 | csharp_new_line_before_catch = true
187 | csharp_new_line_before_finally = true
188 | csharp_new_line_before_members_in_object_initializers = true
189 | csharp_new_line_before_members_in_anonymous_types = true
190 | csharp_new_line_between_query_expression_clauses = true
191 | # Indentation options
192 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#indentation-options
193 | csharp_indent_case_contents = true
194 | csharp_indent_switch_labels = true
195 | csharp_indent_labels = no_change
196 | csharp_indent_block_contents = true
197 | csharp_indent_braces = false
198 | csharp_indent_case_contents_when_block = false
199 | # Spacing options
200 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#spacing-options
201 | csharp_space_after_cast = false
202 | csharp_space_after_keywords_in_control_flow_statements = true
203 | csharp_space_between_parentheses = false
204 | csharp_space_before_colon_in_inheritance_clause = true
205 | csharp_space_after_colon_in_inheritance_clause = true
206 | csharp_space_around_binary_operators = before_and_after
207 | csharp_space_between_method_declaration_parameter_list_parentheses = false
208 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
209 | csharp_space_between_method_declaration_name_and_open_parenthesis = false
210 | csharp_space_between_method_call_parameter_list_parentheses = false
211 | csharp_space_between_method_call_empty_parameter_list_parentheses = false
212 | csharp_space_between_method_call_name_and_opening_parenthesis = false
213 | csharp_space_after_comma = true
214 | csharp_space_before_comma = false
215 | csharp_space_after_dot = false
216 | csharp_space_before_dot = false
217 | csharp_space_after_semicolon_in_for_statement = true
218 | csharp_space_before_semicolon_in_for_statement = false
219 | csharp_space_around_declaration_statements = false
220 | csharp_space_before_open_square_brackets = false
221 | csharp_space_between_empty_square_brackets = false
222 | csharp_space_between_square_brackets = false
223 | # Wrapping options
224 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#wrap-options
225 | csharp_preserve_single_line_statements = false
226 | csharp_preserve_single_line_blocks = true
227 |
228 | ##########################################
229 | # .NET Naming Conventions
230 | # https://docs.microsoft.com/visualstudio/ide/editorconfig-naming-conventions
231 | ##########################################
232 |
233 | [*.{cs,csx,cake,vb}]
234 |
235 | ##########################################
236 | # Styles
237 | ##########################################
238 |
239 | # camel_case_style - Define the camelCase style
240 | dotnet_naming_style.camel_case_style.capitalization = camel_case
241 | # pascal_case_style - Define the PascalCase style
242 | dotnet_naming_style.pascal_case_style.capitalization = pascal_case
243 | # first_upper_style - The first character must start with an upper-case character
244 | dotnet_naming_style.first_upper_style.capitalization = first_word_upper
245 | # prefix_interface_with_i_style - Interfaces must be PascalCase and the first character of an interface must be an 'I'
246 | dotnet_naming_style.prefix_interface_with_i_style.capitalization = pascal_case
247 | dotnet_naming_style.prefix_interface_with_i_style.required_prefix = I
248 | # prefix_type_parameters_with_t_style - Generic Type Parameters must be PascalCase and the first character must be a 'T'
249 | dotnet_naming_style.prefix_type_parameters_with_t_style.capitalization = pascal_case
250 | dotnet_naming_style.prefix_type_parameters_with_t_style.required_prefix = T
251 | # disallowed_style - Anything that has this style applied is marked as disallowed
252 | dotnet_naming_style.disallowed_style.capitalization = pascal_case
253 | dotnet_naming_style.disallowed_style.required_prefix = ____RULE_VIOLATION____
254 | dotnet_naming_style.disallowed_style.required_suffix = ____RULE_VIOLATION____
255 | # internal_error_style - This style should never occur... if it does, it's indicates a bug in file or in the parser using the file
256 | dotnet_naming_style.internal_error_style.capitalization = pascal_case
257 | dotnet_naming_style.internal_error_style.required_prefix = ____INTERNAL_ERROR____
258 | dotnet_naming_style.internal_error_style.required_suffix = ____INTERNAL_ERROR____
259 |
260 | ##########################################
261 | # .NET Design Guideline Field Naming Rules
262 | # Naming rules for fields follow the .NET Framework design guidelines
263 | # https://docs.microsoft.com/dotnet/standard/design-guidelines/index
264 | ##########################################
265 |
266 | # All public/protected/protected_internal constant fields must be PascalCase
267 | # https://docs.microsoft.com/dotnet/standard/design-guidelines/field
268 | dotnet_naming_symbols.public_protected_constant_fields_group.applicable_accessibilities = public, protected, protected_internal
269 | dotnet_naming_symbols.public_protected_constant_fields_group.required_modifiers = const
270 | dotnet_naming_symbols.public_protected_constant_fields_group.applicable_kinds = field
271 | dotnet_naming_rule.public_protected_constant_fields_must_be_pascal_case_rule.symbols = public_protected_constant_fields_group
272 | dotnet_naming_rule.public_protected_constant_fields_must_be_pascal_case_rule.style = pascal_case_style
273 | dotnet_naming_rule.public_protected_constant_fields_must_be_pascal_case_rule.severity = warning
274 |
275 | # All public/protected/protected_internal static readonly fields must be PascalCase
276 | # https://docs.microsoft.com/dotnet/standard/design-guidelines/field
277 | dotnet_naming_symbols.public_protected_static_readonly_fields_group.applicable_accessibilities = public, protected, protected_internal
278 | dotnet_naming_symbols.public_protected_static_readonly_fields_group.required_modifiers = static, readonly
279 | dotnet_naming_symbols.public_protected_static_readonly_fields_group.applicable_kinds = field
280 | dotnet_naming_rule.public_protected_static_readonly_fields_must_be_pascal_case_rule.symbols = public_protected_static_readonly_fields_group
281 | dotnet_naming_rule.public_protected_static_readonly_fields_must_be_pascal_case_rule.style = pascal_case_style
282 | dotnet_naming_rule.public_protected_static_readonly_fields_must_be_pascal_case_rule.severity = warning
283 |
284 | # No other public/protected/protected_internal fields are allowed
285 | # https://docs.microsoft.com/dotnet/standard/design-guidelines/field
286 | dotnet_naming_symbols.other_public_protected_fields_group.applicable_accessibilities = public, protected, protected_internal
287 | dotnet_naming_symbols.other_public_protected_fields_group.applicable_kinds = field
288 | dotnet_naming_rule.other_public_protected_fields_disallowed_rule.symbols = other_public_protected_fields_group
289 | dotnet_naming_rule.other_public_protected_fields_disallowed_rule.style = disallowed_style
290 | dotnet_naming_rule.other_public_protected_fields_disallowed_rule.severity = error
291 |
292 | ##########################################
293 | # StyleCop Field Naming Rules
294 | # Naming rules for fields follow the StyleCop analyzers
295 | # This does not override any rules using disallowed_style above
296 | # https://github.com/DotNetAnalyzers/StyleCopAnalyzers
297 | ##########################################
298 |
299 | # All constant fields must be PascalCase
300 | # https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1303.md
301 | dotnet_naming_symbols.stylecop_constant_fields_group.applicable_accessibilities = public, internal, protected_internal, protected, private_protected, private
302 | dotnet_naming_symbols.stylecop_constant_fields_group.required_modifiers = const
303 | dotnet_naming_symbols.stylecop_constant_fields_group.applicable_kinds = field
304 | dotnet_naming_rule.stylecop_constant_fields_must_be_pascal_case_rule.symbols = stylecop_constant_fields_group
305 | dotnet_naming_rule.stylecop_constant_fields_must_be_pascal_case_rule.style = pascal_case_style
306 | dotnet_naming_rule.stylecop_constant_fields_must_be_pascal_case_rule.severity = warning
307 |
308 | # All static readonly fields must be PascalCase
309 | # https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1311.md
310 | dotnet_naming_symbols.stylecop_static_readonly_fields_group.applicable_accessibilities = public, internal, protected_internal, protected, private_protected, private
311 | dotnet_naming_symbols.stylecop_static_readonly_fields_group.required_modifiers = static, readonly
312 | dotnet_naming_symbols.stylecop_static_readonly_fields_group.applicable_kinds = field
313 | dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.symbols = stylecop_static_readonly_fields_group
314 | dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.style = pascal_case_style
315 | dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.severity = warning
316 |
317 | # No non-private instance fields are allowed
318 | # https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1401.md
319 | dotnet_naming_symbols.stylecop_fields_must_be_private_group.applicable_accessibilities = public, internal, protected_internal, protected, private_protected
320 | dotnet_naming_symbols.stylecop_fields_must_be_private_group.applicable_kinds = field
321 | dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.symbols = stylecop_fields_must_be_private_group
322 | dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.style = disallowed_style
323 | dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.severity = error
324 |
325 | # Private fields must be camelCase
326 | # https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1306.md
327 | dotnet_naming_symbols.stylecop_private_fields_group.applicable_accessibilities = private
328 | dotnet_naming_symbols.stylecop_private_fields_group.applicable_kinds = field
329 | dotnet_naming_rule.stylecop_private_fields_must_be_camel_case_rule.symbols = stylecop_private_fields_group
330 | dotnet_naming_rule.stylecop_private_fields_must_be_camel_case_rule.style = camel_case_style
331 | dotnet_naming_rule.stylecop_private_fields_must_be_camel_case_rule.severity = warning
332 |
333 | # Local variables must be camelCase
334 | # https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1312.md
335 | dotnet_naming_symbols.stylecop_local_fields_group.applicable_accessibilities = local
336 | dotnet_naming_symbols.stylecop_local_fields_group.applicable_kinds = local
337 | dotnet_naming_rule.stylecop_local_fields_must_be_camel_case_rule.symbols = stylecop_local_fields_group
338 | dotnet_naming_rule.stylecop_local_fields_must_be_camel_case_rule.style = camel_case_style
339 | dotnet_naming_rule.stylecop_local_fields_must_be_camel_case_rule.severity = silent
340 |
341 | # This rule should never fire. However, it's included for at least two purposes:
342 | # First, it helps to understand, reason about, and root-case certain types of issues, such as bugs in .editorconfig parsers.
343 | # Second, it helps to raise immediate awareness if a new field type is added (as occurred recently in C#).
344 | dotnet_naming_symbols.sanity_check_uncovered_field_case_group.applicable_accessibilities = *
345 | dotnet_naming_symbols.sanity_check_uncovered_field_case_group.applicable_kinds = field
346 | dotnet_naming_rule.sanity_check_uncovered_field_case_rule.symbols = sanity_check_uncovered_field_case_group
347 | dotnet_naming_rule.sanity_check_uncovered_field_case_rule.style = internal_error_style
348 | dotnet_naming_rule.sanity_check_uncovered_field_case_rule.severity = error
349 |
350 |
351 | ##########################################
352 | # Other Naming Rules
353 | ##########################################
354 |
355 | # All of the following must be PascalCase:
356 | # - Namespaces
357 | # https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-namespaces
358 | # https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1300.md
359 | # - Classes and Enumerations
360 | # https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces
361 | # https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1300.md
362 | # - Delegates
363 | # https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces#names-of-common-types
364 | # - Constructors, Properties, Events, Methods
365 | # https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-type-members
366 | dotnet_naming_symbols.element_group.applicable_kinds = namespace, class, enum, struct, delegate, event, method, property
367 | dotnet_naming_rule.element_rule.symbols = element_group
368 | dotnet_naming_rule.element_rule.style = pascal_case_style
369 | dotnet_naming_rule.element_rule.severity = warning
370 |
371 | # Interfaces use PascalCase and are prefixed with uppercase 'I'
372 | # https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces
373 | dotnet_naming_symbols.interface_group.applicable_kinds = interface
374 | dotnet_naming_rule.interface_rule.symbols = interface_group
375 | dotnet_naming_rule.interface_rule.style = prefix_interface_with_i_style
376 | dotnet_naming_rule.interface_rule.severity = warning
377 |
378 | # Generics Type Parameters use PascalCase and are prefixed with uppercase 'T'
379 | # https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces
380 | dotnet_naming_symbols.type_parameter_group.applicable_kinds = type_parameter
381 | dotnet_naming_rule.type_parameter_rule.symbols = type_parameter_group
382 | dotnet_naming_rule.type_parameter_rule.style = prefix_type_parameters_with_t_style
383 | dotnet_naming_rule.type_parameter_rule.severity = warning
384 |
385 | # Function parameters use camelCase
386 | # https://docs.microsoft.com/dotnet/standard/design-guidelines/naming-parameters
387 | dotnet_naming_symbols.parameters_group.applicable_kinds = parameter
388 | dotnet_naming_rule.parameters_rule.symbols = parameters_group
389 | dotnet_naming_rule.parameters_rule.style = camel_case_style
390 | dotnet_naming_rule.parameters_rule.severity = warning
391 |
392 | ##########################################
393 | # License
394 | ##########################################
395 | # The following applies as to the .editorconfig file ONLY, and is
396 | # included below for reference, per the requirements of the license
397 | # corresponding to this .editorconfig file.
398 | # See: https://github.com/RehanSaeed/EditorConfig
399 | #
400 | # MIT License
401 | #
402 | # Copyright (c) 2017-2019 Muhammad Rehan Saeed
403 | # Copyright (c) 2019 Henry Gabryjelski
404 | #
405 | # Permission is hereby granted, free of charge, to any
406 | # person obtaining a copy of this software and associated
407 | # documentation files (the "Software"), to deal in the
408 | # Software without restriction, including without limitation
409 | # the rights to use, copy, modify, merge, publish, distribute,
410 | # sublicense, and/or sell copies of the Software, and to permit
411 | # persons to whom the Software is furnished to do so, subject
412 | # to the following conditions:
413 | #
414 | # The above copyright notice and this permission notice shall be
415 | # included in all copies or substantial portions of the Software.
416 | #
417 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
418 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
419 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
420 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
421 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
422 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
423 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
424 | # OTHER DEALINGS IN THE SOFTWARE.
425 | ##########################################
426 |
--------------------------------------------------------------------------------