Skip to content

Commit f2e8970

Browse files
authored
OpenAPI implementation for InstantAPIs (#47)
1 parent 543043b commit f2e8970

File tree

5 files changed

+150
-71
lines changed

5 files changed

+150
-71
lines changed

Fritz.InstantAPIs/Fritz.InstantAPIs.csproj

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@
2020

2121

2222
<Target Name="PreparePackageReleaseNotesFromFile" BeforeTargets="GenerateNuspec">
23-
<ReadLinesFromFile File="../RELEASE_NOTES.txt" >
24-
<Output TaskParameter="Lines" ItemName="ReleaseNoteLines"/>
23+
<ReadLinesFromFile File="../RELEASE_NOTES.txt">
24+
<Output TaskParameter="Lines" ItemName="ReleaseNoteLines" />
2525
</ReadLinesFromFile>
2626
<PropertyGroup>
2727
<PackageReleaseNotes>@(ReleaseNoteLines, '%0a')</PackageReleaseNotes>
@@ -40,6 +40,7 @@
4040
</None>
4141
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.2" />
4242
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
43+
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
4344
</ItemGroup>
4445

4546
<ItemGroup>
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
namespace Microsoft.Extensions.DependencyInjection;
2+
3+
public static class InstantAPIsServiceCollectionExtensions
4+
{
5+
public static IServiceCollection AddInstantAPIs(this IServiceCollection services, Action<InstantAPIsServiceOptions>? setupAction = null)
6+
{
7+
var options = new InstantAPIsServiceOptions();
8+
9+
// Get the service options
10+
setupAction?.Invoke(options);
11+
12+
if (options.EnableSwagger == null)
13+
{
14+
options.EnableSwagger = EnableSwagger.DevelopmentOnly;
15+
}
16+
17+
// Add and configure Swagger services if it is enabled
18+
if (options.EnableSwagger != EnableSwagger.None)
19+
{
20+
services.AddEndpointsApiExplorer();
21+
services.AddSwaggerGen(options.Swagger);
22+
}
23+
24+
// Register the required options so that it can be accessed by InstantAPIs middleware
25+
services.Configure<InstantAPIsServiceOptions>(config =>
26+
{
27+
config.EnableSwagger = options.EnableSwagger;
28+
});
29+
30+
return services;
31+
}
32+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using Swashbuckle.AspNetCore.SwaggerGen;
2+
3+
namespace Fritz.InstantAPIs;
4+
5+
public enum EnableSwagger
6+
{
7+
None,
8+
DevelopmentOnly,
9+
Always
10+
}
11+
12+
public class InstantAPIsServiceOptions
13+
{
14+
15+
public EnableSwagger? EnableSwagger { get; set; }
16+
public Action<SwaggerGenOptions>? Swagger { get; set; }
17+
}
Lines changed: 81 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,92 @@
11
using Microsoft.AspNetCore.Routing;
22
using Microsoft.Extensions.DependencyInjection;
3+
using Microsoft.Extensions.Hosting;
4+
using Microsoft.Extensions.Options;
35
using System.Reflection;
46

57
namespace Microsoft.AspNetCore.Builder;
68

79
public static class WebApplicationExtensions
810
{
911

10-
private static InstantAPIsConfig Configuration { get; set; } = new();
11-
12-
public static IEndpointRouteBuilder MapInstantAPIs<D>(this IEndpointRouteBuilder app, Action<InstantAPIsConfigBuilder<D>> options = null) where D: DbContext
13-
{
14-
15-
if (app is IApplicationBuilder applicationBuilder)
16-
{
17-
var ctx = applicationBuilder.ApplicationServices.CreateScope().ServiceProvider.GetService(typeof(D)) as D;
18-
var builder = new InstantAPIsConfigBuilder<D>(ctx);
19-
if (options != null)
20-
{
21-
options(builder);
22-
Configuration = builder.Build();
23-
}
24-
}
25-
26-
// Get the tables on the DbContext
27-
var dbTables = GetDbTablesForContext<D>();
28-
29-
var requestedTables = !Configuration.Tables.Any() ?
30-
dbTables :
31-
Configuration.Tables.Where(t => dbTables.Any(db => db.Name.Equals(t.Name, StringComparison.OrdinalIgnoreCase))).ToArray();
32-
33-
var allMethods = typeof(MapApiExtensions).GetMethods(BindingFlags.NonPublic | BindingFlags.Static).Where(m => m.Name.StartsWith("Map")).ToArray();
34-
var initialize = typeof(MapApiExtensions).GetMethod("Initialize", BindingFlags.NonPublic | BindingFlags.Static);
35-
foreach (var table in requestedTables)
36-
{
37-
38-
// The default URL for an InstantAPI is /api/TABLENAME
39-
var url = $"/api/{table.Name}";
40-
41-
initialize.MakeGenericMethod(typeof(D), table.InstanceType).Invoke(null, null);
42-
43-
// The remaining private static methods in this class build out the Mapped API methods..
44-
// let's use some reflection to get them
45-
foreach (var method in allMethods)
46-
{
47-
48-
var sigAttr = method.CustomAttributes.First(x => x.AttributeType == typeof(ApiMethodAttribute)).ConstructorArguments.First();
49-
var methodType = (ApiMethodsToGenerate)sigAttr.Value;
50-
if ((table.ApiMethodsToGenerate & methodType) != methodType) continue;
51-
52-
var genericMethod = method.MakeGenericMethod(typeof(D), table.InstanceType);
53-
genericMethod.Invoke(null, new object[] { app, url });
54-
}
55-
56-
}
57-
58-
return app;
59-
}
60-
61-
internal static IEnumerable<TypeTable> GetDbTablesForContext<D>() where D : DbContext
62-
{
63-
return typeof(D).GetProperties(BindingFlags.Instance | BindingFlags.Public)
64-
.Where(x => x.PropertyType.FullName.StartsWith("Microsoft.EntityFrameworkCore.DbSet"))
65-
.Select(x => new TypeTable { Name = x.Name, InstanceType = x.PropertyType.GenericTypeArguments.First() })
66-
.ToArray();
67-
}
68-
69-
internal class TypeTable
70-
{
71-
public string Name { get; set; }
72-
public Type InstanceType { get; set; }
73-
public ApiMethodsToGenerate ApiMethodsToGenerate { get; set; } = ApiMethodsToGenerate.All;
74-
}
12+
private static InstantAPIsConfig Configuration { get; set; } = new();
13+
14+
public static IEndpointRouteBuilder MapInstantAPIs<D>(this IEndpointRouteBuilder app, Action<InstantAPIsConfigBuilder<D>> options = null) where D : DbContext
15+
{
16+
if (app is IApplicationBuilder applicationBuilder)
17+
{
18+
// Check if AddInstantAPIs was called by getting the service options and evaluate EnableSwagger property
19+
var serviceOptions = applicationBuilder.ApplicationServices.GetRequiredService<IOptions<InstantAPIsServiceOptions>>().Value;
20+
if (serviceOptions == null || serviceOptions.EnableSwagger == null)
21+
{
22+
throw new ArgumentException("Call builder.Services.AddInstantAPIs(options) before MapInstantAPIs.");
23+
}
24+
25+
var webApp = (WebApplication)app;
26+
if (serviceOptions.EnableSwagger == EnableSwagger.Always ||
27+
(serviceOptions.EnableSwagger == EnableSwagger.DevelopmentOnly && webApp.Environment.IsDevelopment()))
28+
{
29+
applicationBuilder.UseSwagger();
30+
applicationBuilder.UseSwaggerUI();
31+
}
32+
33+
var ctx = applicationBuilder.ApplicationServices.CreateScope().ServiceProvider.GetService(typeof(D)) as D;
34+
var builder = new InstantAPIsConfigBuilder<D>(ctx);
35+
if (options != null)
36+
{
37+
options(builder);
38+
Configuration = builder.Build();
39+
}
40+
}
41+
42+
// Get the tables on the DbContext
43+
var dbTables = GetDbTablesForContext<D>();
44+
45+
var requestedTables = !Configuration.Tables.Any() ?
46+
dbTables :
47+
Configuration.Tables.Where(t => dbTables.Any(db => db.Name.Equals(t.Name, StringComparison.OrdinalIgnoreCase))).ToArray();
48+
49+
var allMethods = typeof(MapApiExtensions).GetMethods(BindingFlags.NonPublic | BindingFlags.Static).Where(m => m.Name.StartsWith("Map")).ToArray();
50+
var initialize = typeof(MapApiExtensions).GetMethod("Initialize", BindingFlags.NonPublic | BindingFlags.Static);
51+
foreach (var table in requestedTables)
52+
{
53+
54+
// The default URL for an InstantAPI is /api/TABLENAME
55+
var url = $"/api/{table.Name}";
56+
57+
initialize.MakeGenericMethod(typeof(D), table.InstanceType).Invoke(null, null);
58+
59+
// The remaining private static methods in this class build out the Mapped API methods..
60+
// let's use some reflection to get them
61+
foreach (var method in allMethods)
62+
{
63+
64+
var sigAttr = method.CustomAttributes.First(x => x.AttributeType == typeof(ApiMethodAttribute)).ConstructorArguments.First();
65+
var methodType = (ApiMethodsToGenerate)sigAttr.Value;
66+
if ((table.ApiMethodsToGenerate & methodType) != methodType) continue;
67+
68+
var genericMethod = method.MakeGenericMethod(typeof(D), table.InstanceType);
69+
genericMethod.Invoke(null, new object[] { app, url });
70+
}
71+
72+
}
73+
74+
return app;
75+
}
76+
77+
internal static IEnumerable<TypeTable> GetDbTablesForContext<D>() where D : DbContext
78+
{
79+
return typeof(D).GetProperties(BindingFlags.Instance | BindingFlags.Public)
80+
.Where(x => x.PropertyType.FullName.StartsWith("Microsoft.EntityFrameworkCore.DbSet"))
81+
.Select(x => new TypeTable { Name = x.Name, InstanceType = x.PropertyType.GenericTypeArguments.First() })
82+
.ToArray();
83+
}
84+
85+
internal class TypeTable
86+
{
87+
public string Name { get; set; }
88+
public Type InstanceType { get; set; }
89+
public ApiMethodsToGenerate ApiMethodsToGenerate { get; set; } = ApiMethodsToGenerate.All;
90+
}
7591

7692
}

WorkingApi/Program.cs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,26 @@
11
using Fritz.InstantAPIs;
22
using Microsoft.EntityFrameworkCore;
3+
using Microsoft.OpenApi.Models;
34
using System.Diagnostics;
45
using WorkingApi;
56

67
var builder = WebApplication.CreateBuilder(args);
78

89
builder.Services.AddSqlite<MyContext>("Data Source=contacts.db");
9-
builder.Services.AddEndpointsApiExplorer();
10-
builder.Services.AddSwaggerGen();
10+
//builder.Services.AddEndpointsApiExplorer();
11+
//builder.Services.AddSwaggerGen();
12+
builder.Services.AddInstantAPIs(options =>
13+
{
14+
options.EnableSwagger = Fritz.InstantAPIs.EnableSwagger.DevelopmentOnly;
15+
options.Swagger = config =>
16+
{
17+
config.SwaggerDoc("v1", new OpenApiInfo
18+
{
19+
Title = "My Working API",
20+
Description = "An ASP.NET Core Web API"
21+
});
22+
};
23+
});
1124

1225
var app = builder.Build();
1326

@@ -19,7 +32,7 @@
1932
});
2033
Console.WriteLine($"Elapsed time to build InstantAPIs: {sw.Elapsed}");
2134

22-
app.UseSwagger();
23-
app.UseSwaggerUI();
35+
//app.UseSwagger();
36+
//app.UseSwaggerUI();
2437

2538
app.Run();

0 commit comments

Comments
 (0)