Skip to content

Commit 73011f6

Browse files
authored
Refactoring Open API Info Metadata (#88)
1 parent 46a318c commit 73011f6

File tree

32 files changed

+670
-57
lines changed

32 files changed

+670
-57
lines changed

.github/workflows/build.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ jobs:
3939
shell: pwsh
4040
run: |
4141
dir
42-
msbuild . /p:Configuration=Debug /p:Verbosity=minimal
42+
dotnet build . -c Debug -v minimal
4343
4444
- name: Test solution
4545
shell: pwsh

.github/workflows/pr.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,10 @@ jobs:
3737
shell: pwsh
3838
run: |
3939
dir
40-
msbuild . /p:Configuration=Release /p:Verbosity=minimal
40+
dotnet build . -c Debug -v minimal
4141
4242
- name: Test solution
4343
shell: pwsh
4444
run: |
4545
dir
46-
dotnet test . -c Release
46+
dotnet test . -c Debug

docs/app-settings.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,15 @@
33
![Build and Test](https://github.com/aliencube/AzureFunctions.Extensions/workflows/Build%20and%20Test/badge.svg) [![](https://img.shields.io/nuget/dt/Aliencube.AzureFunctions.Extensions.Configuration.AppSettings.svg)](https://www.nuget.org/packages/Aliencube.AzureFunctions.Extensions.Configuration.AppSettings/) [![](https://img.shields.io/nuget/v/Aliencube.AzureFunctions.Extensions.Configuration.AppSettings.svg)](https://www.nuget.org/packages/Aliencube.AzureFunctions.Extensions.Configuration.AppSettings/)
44

55
This is a base app settings environment variables deserialised. This MUST be inherited for use.
6+
7+
```csharp
8+
public abstract class OpenApiAppSettingsBase : AppSettingsBase
9+
{
10+
public OpenApiAppSettingsBase()
11+
: base()
12+
{
13+
...
14+
}
15+
...
16+
}
17+
```

docs/dependency-injection.md

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,23 @@ This is only applicable to Azure Functions V2, as V2 runtime now take out the `s
1212

1313
### `StartUp` ###
1414

15-
In order to use dependency injections, all dependencies should be registered through the `StartUp` class implementing the `IWebJobsStartup` interface.
15+
In order to use dependency injections, all dependencies should be registered through the `StartUp` class inheriting the `FunctionsStartup` class.
1616

1717
```csharp
18-
[assembly: WebJobsStartup(typeof(StartUp))]
18+
[assembly: FunctionsStartup(typeof(Aliencube.AzureFunctions.FunctionAppV2.StartUp))]
1919
namespace Aliencube.AzureFunctions.FunctionAppV2
2020
{
21-
public class StartUp : IWebJobsStartup
21+
public class AppSettings : AppSettingsBase
2222
{
23-
public void Configure(IWebJobsBuilder builder)
23+
public AppSettings()
24+
: base()
25+
{
26+
}
27+
}
28+
29+
public class StartUp : FunctionsStartup
30+
{
31+
public override void Configure(IFunctionsHostBuilder builder)
2432
{
2533
builder.Services.AddSingleton<AppSettings>();
2634

@@ -73,6 +81,14 @@ For Azure Function V1, the most efficient way for dependency injection would be
7381
In order to use dependency injection, all dependencies should be registered beforehand. The `Module` class needs to be inherited then all dependencies are registered within the `Load(IServiceCollection services)` method.
7482

7583
```csharp
84+
public class AppSettings : AppSettingsBase
85+
{
86+
public AppSettings()
87+
: base()
88+
{
89+
}
90+
}
91+
7692
public class StartUp : Module
7793
{
7894
public override void Load(IServiceCollection services)

docs/openapi.md

Lines changed: 79 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,16 @@ public static async Task<IActionResult> RenderSwaggerDocument(
4141
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "swagger.json")] HttpRequest req,
4242
ILogger log)
4343
{
44-
var settings = new AppSettings();
44+
var openApiInfo = OpenApiInfoResolver.Resolve();
45+
var host = HostJsonResolver.Resolve();
46+
var httpSettings = host.GetHttpSettings();
47+
4548
var filter = new RouteConstraintFilter();
4649
var helper = new DocumentHelper(filter);
4750
var document = new Document(helper);
4851
var result = await document.InitialiseDocument()
49-
.AddMetadata(settings.OpenApiInfo)
50-
.AddServer(req, settings.HttpSettings.RoutePrefix)
52+
.AddMetadata(openApiInfo)
53+
.AddServer(req, httpSettings.RoutePrefix)
5154
.Build(Assembly.GetExecutingAssembly(), new CamelCaseNamingStrategy())
5255
.RenderAsync(OpenApiSpecVersion.OpenApi2_0, OpenApiFormat.Json)
5356
.ConfigureAwait(false);
@@ -76,12 +79,16 @@ public static async Task<IActionResult> RenderSwaggerUI(
7679
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "swagger/ui")] HttpRequest req,
7780
ILogger log)
7881
{
79-
var settings = new AppSettings();
82+
var openApiInfo = OpenApiInfoResolver.Resolve();
83+
var host = HostJsonResolver.Resolve();
84+
var httpSettings = host.GetHttpSettings();
85+
var swaggerAuthKey = ConfigurationResolver.GetValue<string>("OpenApi:ApiKey");
86+
8087
var ui = new SwaggerUI();
81-
var result = await ui.AddMetadata(settings.OpenApiInfo)
82-
.AddServer(req, settings.HttpSettings.RoutePrefix)
88+
var result = await ui.AddMetadata(openApiInfo)
89+
.AddServer(req, httpSettings.RoutePrefix)
8390
.BuildAsync()
84-
.RenderAsync("swagger.json", settings.SwaggerAuthKey)
91+
.RenderAsync("swagger.json", swaggerAuthKey)
8592
.ConfigureAwait(false);
8693
var response = new ContentResult()
8794
{
@@ -95,7 +102,71 @@ public static async Task<IActionResult> RenderSwaggerUI(
95102
```
96103

97104

98-
## App Settings ##
105+
## Open API Metadata ##
106+
107+
To generate an Open API document, [OpenApiInfo object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#infoObject) needs to be defined. This information can be declared in three places &ndash; `host.json`, `openapisettings.json` or `local.settings.json`.
108+
109+
This library looks for the information in the following order:
110+
111+
1. `host.json`
112+
2. `openapisettings.json`
113+
3. `local.settings.json` or App Settings blade
114+
115+
116+
### `host.json` ###
117+
118+
Although it has not been officially accepted to be a part of `host.json`, the Open API metadata still can be stored in it like:
119+
120+
```json
121+
{
122+
...
123+
"openApi": {
124+
"info": {
125+
"version": "3.0.0",
126+
"title": "Open API Sample on Azure Functions",
127+
"description": "A sample API that runs on Azure Functions 3.x using Open API specification - from **host. json**.",
128+
"termsOfService": "https://github.com/aliencube/AzureFunctions.Extensions",
129+
"contact": {
130+
"name": "Aliencube Community",
131+
"email": "[email protected]",
132+
"url": "https://github.com/aliencube/AzureFunctions.Extensions/issues"
133+
},
134+
"license": {
135+
"name": "MIT",
136+
"url": "http://opensource.org/licenses/MIT"
137+
}
138+
}
139+
}
140+
...
141+
}
142+
```
143+
144+
145+
### `openapisettings.json` ###
146+
147+
The Open API metadata can be defined in a separate file, `openapisettings.json` like:
148+
149+
```json
150+
{
151+
"info": {
152+
"version": "3.0.0",
153+
"title": "Open API Sample on Azure Functions",
154+
"description": "A sample API that runs on Azure Functions 3.x using Open API specification - from **openapisettings.json**.",
155+
"termsOfService": "https://github.com/aliencube/AzureFunctions.Extensions",
156+
"contact": {
157+
"name": "Aliencube Community",
158+
"email": "[email protected]",
159+
"url": "https://github.com/aliencube/AzureFunctions.Extensions/issues"
160+
},
161+
"license": {
162+
"name": "MIT",
163+
"url": "http://opensource.org/licenses/MIT"
164+
}
165+
}
166+
}
167+
```
168+
169+
### `local.settings.json` or App Settings ###
99170

100171
On either your `local.settings.json` or App Settings on Azure Functions instance, those details should be set up to render Open API metadata:
101172

src/Aliencube.AzureFunctions.Extensions.Configuration.AppSettings/Aliencube.AzureFunctions.Extensions.Configuration.AppSettings.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
<TargetFrameworks>net461;netstandard2.0</TargetFrameworks>
55
<IsPackable>true</IsPackable>
66
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
7+
    <AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
78
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
89
<PackageId>Aliencube.AzureFunctions.Extensions.Configuration.AppSettings</PackageId>
910
<Owners>Aliencube</Owners>
Lines changed: 3 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
1-
using System;
2-
using System.IO;
3-
using System.Linq;
4-
using System.Reflection;
1+
using Aliencube.AzureFunctions.Extensions.Configuration.AppSettings.Resolvers;
52

63
using Microsoft.Extensions.Configuration;
74

8-
using OperatingSystem = Aliencube.AzureFunctions.Extensions.Configuration.AppSettings.Extensions.OperationSystem;
9-
105
namespace Aliencube.AzureFunctions.Extensions.Configuration.AppSettings
116
{
127
/// <summary>
@@ -19,9 +14,7 @@ public abstract class AppSettingsBase
1914
/// </summary>
2015
protected AppSettingsBase()
2116
{
22-
this.Config = new ConfigurationBuilder()
23-
.AddEnvironmentVariables()
24-
.Build();
17+
this.Config = ConfigurationResolver.Resolve();
2518
}
2619

2720
/// <summary>
@@ -35,22 +28,7 @@ protected AppSettingsBase()
3528
/// <returns></returns>
3629
protected string GetBasePath()
3730
{
38-
var location = Assembly.GetExecutingAssembly().Location;
39-
var segments = location.Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries).ToList();
40-
var basePath = string.Join(Path.DirectorySeparatorChar.ToString(), segments.Take(segments.Count - 2));
41-
42-
if (!OperatingSystem.IsWindows())
43-
{
44-
basePath = $"/{basePath}";
45-
}
46-
#if NET461
47-
var scriptRootPath = this.Config.GetValue<string>("AzureWebJobsScriptRoot");
48-
if (!string.IsNullOrWhiteSpace(scriptRootPath))
49-
{
50-
basePath = scriptRootPath;
51-
}
52-
#endif
53-
return basePath;
31+
return ConfigurationResolver.GetBasePath(this.Config);
5432
}
5533
}
5634
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Reflection;
6+
7+
using Microsoft.Extensions.Configuration;
8+
9+
using OperatingSystem = Aliencube.AzureFunctions.Extensions.Configuration.AppSettings.Extensions.OperationSystem;
10+
11+
namespace Aliencube.AzureFunctions.Extensions.Configuration.AppSettings.Resolvers
12+
{
13+
/// <summary>
14+
/// This represents the resolver entity for configuration.
15+
/// </summary>
16+
public static class ConfigurationResolver
17+
{
18+
/// <summary>
19+
/// Gets the <see cref="IConfiguration"/> instance from the environment variables - either local.settings.json or App Settings blade.
20+
/// </summary>
21+
public static IConfiguration Resolve()
22+
{
23+
var config = new ConfigurationBuilder()
24+
.AddEnvironmentVariables()
25+
.Build();
26+
27+
return config;
28+
}
29+
30+
/// <summary>
31+
/// Gets the configuration value.
32+
/// </summary>
33+
/// <typeparam name="T">Type of return value.</typeparam>
34+
/// <param name="key">Key for lookup.</param>
35+
/// <param name="config"><see cref="IConfiguration"/> instance from the environment variables - either local.settings.json or App Settings blade.</param>
36+
/// <returns>Returns the value.</returns>
37+
public static T GetValue<T>(string key, IConfiguration config = null)
38+
{
39+
if (config == null)
40+
{
41+
config = Resolve();
42+
}
43+
44+
var value = config.GetValue<T>(key);
45+
46+
return value;
47+
}
48+
49+
/// <summary>
50+
/// Gets the base path of the executing Azure Functions assembly.
51+
/// </summary>
52+
/// <param name="environmentVariables"><see cref="IConfiguration"/> instance representing environment variables from either local.settings.json or App Settings blade.</param>
53+
/// <returns>Returns the base path of the executing Azure Functions assembly.</returns>
54+
public static string GetBasePath(IConfiguration environmentVariables)
55+
{
56+
var location = Assembly.GetExecutingAssembly().Location;
57+
var segments = location.Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries).ToList();
58+
var basePath = string.Join(Path.DirectorySeparatorChar.ToString(), segments.Take(CountDirectories(segments)));
59+
60+
if (!OperatingSystem.IsWindows())
61+
{
62+
basePath = $"/{basePath}";
63+
}
64+
#if NET461
65+
var scriptRootPath = environmentVariables.GetValue<string>("AzureWebJobsScriptRoot");
66+
if (!string.IsNullOrWhiteSpace(scriptRootPath))
67+
{
68+
basePath = scriptRootPath;
69+
}
70+
#endif
71+
return basePath;
72+
}
73+
74+
private static int CountDirectories(List<string> segments)
75+
{
76+
var bin = segments[segments.Count - 2];
77+
if (bin == "bin")
78+
{
79+
return segments.Count - 2;
80+
}
81+
82+
return segments.Count - 1;
83+
}
84+
}
85+
}

src/Aliencube.AzureFunctions.Extensions.Configuration.Json/Aliencube.AzureFunctions.Extensions.Configuration.Json.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
<TargetFramework>net461</TargetFramework>
55
<IsPackable>true</IsPackable>
66
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
7+
    <AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
78
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
89
<PackageId>Aliencube.AzureFunctions.Extensions.Configuration.Json</PackageId>
910
<Owners>Aliencube</Owners>

src/Aliencube.AzureFunctions.Extensions.DependencyInjection/Aliencube.AzureFunctions.Extensions.DependencyInjection.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
<TargetFrameworks>net461;netstandard2.0</TargetFrameworks>
55
<IsPackable>true</IsPackable>
66
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
7+
    <AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
78
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
89
<PackageId>Aliencube.AzureFunctions.Extensions.DependencyInjection</PackageId>
910
<Owners>Aliencube</Owners>

0 commit comments

Comments
 (0)