Skip to content

Commit c614f7a

Browse files
author
Dries Verbeke
committed
Enabling multiple data providers
- added an interface with factory - included ef core provider - included json provider - update the test programs to utilize the repository approach
1 parent 3ebe5d5 commit c614f7a

26 files changed

+674
-618
lines changed

InstantAPIs/InstantAPIsBuilder.cs

Lines changed: 43 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,27 @@
1-
using System.Linq.Expressions;
2-
using System.Reflection;
1+
using InstantAPIs.Repositories;
2+
using System.Linq.Expressions;
33

44
namespace Microsoft.AspNetCore.Builder;
55

6-
public class InstantAPIsBuilder<TContext>
7-
where TContext : DbContext
6+
public class InstantAPIsBuilder<TContext>
7+
where TContext : class
88
{
9+
private readonly InstantAPIsOptions _instantApiOptions;
10+
private readonly IContextHelper<TContext> _contextFactory;
11+
private readonly HashSet<InstantAPIsOptions.ITable> _tables = new HashSet<InstantAPIsOptions.ITable>();
12+
private readonly IList<string> _excludedTables = new List<string>();
913

10-
private HashSet<InstantAPIsOptions.ITable> _Config = new();
11-
private Type _ContextType = typeof(TContext);
12-
private TContext _TheContext;
13-
private readonly HashSet<InstantAPIsOptions.ITable> _IncludedTables = new();
14-
private readonly List<string> _ExcludedTables = new();
15-
private const string DEFAULT_URI = "/api/";
14+
public InstantAPIsBuilder(InstantAPIsOptions instantApiOptions, IContextHelper<TContext> contextFactory)
15+
{
16+
_instantApiOptions = instantApiOptions;
17+
_contextFactory = contextFactory;
18+
}
1619

17-
public InstantAPIsBuilder(TContext theContext)
20+
private IEnumerable<InstantAPIsOptions.ITable> DiscoverTables()
1821
{
19-
this._TheContext = theContext;
22+
return _contextFactory != null
23+
? _contextFactory.DiscoverFromContext(_instantApiOptions.DefaultUri)
24+
: Array.Empty<InstantAPIsOptions.ITable>();
2025
}
2126

2227
#region Table Inclusion/Exclusion
@@ -27,14 +32,13 @@ public InstantAPIsBuilder(TContext theContext)
2732
/// <param name="setSelector">Select the EntityFramework DbSet to include - Required</param>
2833
/// <param name="methodsToGenerate">A flags enumerable indicating the methods to generate. By default ALL are generated</param>
2934
/// <returns>Configuration builder with this configuration applied</returns>
30-
public InstantAPIsBuilder<TContext> IncludeTable<TSet, TEntity, TKey>(Expression<Func<TContext, TSet>> setSelector,
31-
InstantAPIsOptions.TableOptions<TEntity, TKey> config, ApiMethodsToGenerate methodsToGenerate = ApiMethodsToGenerate.All, string baseUrl = "")
32-
where TSet : DbSet<TEntity>
35+
public InstantAPIsBuilder<TContext> IncludeTable<TSet, TEntity, TKey>(Expression<Func<TContext, TSet>> setSelector,
36+
InstantAPIsOptions.TableOptions<TEntity, TKey> config, ApiMethodsToGenerate methodsToGenerate = ApiMethodsToGenerate.All,
37+
string baseUrl = "")
38+
where TSet : class
3339
where TEntity : class
3440
{
35-
36-
var theSetType = setSelector.Compile()(_TheContext).GetType().BaseType;
37-
var property = _ContextType.GetProperties().First(p => p.PropertyType == theSetType);
41+
var propertyName = _contextFactory.NameTable(setSelector);
3842

3943
if (!string.IsNullOrEmpty(baseUrl))
4044
{
@@ -50,17 +54,16 @@ public InstantAPIsBuilder<TContext> IncludeTable<TSet, TEntity, TKey>(Expression
5054
}
5155
else
5256
{
53-
baseUrl = string.Concat(DEFAULT_URI, property.Name);
57+
baseUrl = string.Concat(_instantApiOptions.DefaultUri.ToString(), "/", propertyName);
5458
}
5559

56-
var tableApiMapping = new InstantAPIsOptions.Table<TContext, TSet, TEntity, TKey>(property.Name, new Uri(baseUrl, UriKind.Relative), setSelector, config)
57-
{
58-
ApiMethodsToGenerate = methodsToGenerate
60+
var tableApiMapping = new InstantAPIsOptions.Table<TContext, TSet, TEntity, TKey>(propertyName, new Uri(baseUrl, UriKind.Relative), setSelector, config)
61+
{
62+
ApiMethodsToGenerate = methodsToGenerate
5963
};
60-
_IncludedTables.Add(tableApiMapping);
6164

62-
if (_ExcludedTables.Contains(tableApiMapping.Name)) _ExcludedTables.Remove(tableApiMapping.Name);
63-
_IncludedTables.Add(tableApiMapping);
65+
_tables.RemoveWhere(x => x.Name == tableApiMapping.Name);
66+
_tables.Add(tableApiMapping);
6467

6568
return this;
6669

@@ -69,106 +72,39 @@ public InstantAPIsBuilder<TContext> IncludeTable<TSet, TEntity, TKey>(Expression
6972
/// <summary>
7073
/// Exclude individual tables from the API generation. Exclusion takes priority over inclusion
7174
/// </summary>
72-
/// <param name="entitySelector">Select the entity to exclude from generation</param>
75+
/// <param name="setSelector">Select the entity to exclude from generation</param>
7376
/// <returns>Configuration builder with this configuraiton applied</returns>
74-
public InstantAPIsBuilder<TContext> ExcludeTable<T>(Func<TContext, DbSet<T>> entitySelector) where T : class
77+
public InstantAPIsBuilder<TContext> ExcludeTable<TSet>(Expression<Func<TContext, TSet>> setSelector) where TSet : class
7578
{
76-
77-
var theSetType = entitySelector(_TheContext).GetType().BaseType;
78-
var property = _ContextType.GetProperties().First(p => p.PropertyType == theSetType);
79-
80-
if (_IncludedTables.Select(t => t.Name).Contains(property.Name)) _IncludedTables.Remove(_IncludedTables.First(t => t.Name == property.Name));
81-
_ExcludedTables.Add(property.Name);
79+
var propertyName = _contextFactory.NameTable(setSelector);
80+
_excludedTables.Add(propertyName);
8281

8382
return this;
84-
8583
}
8684

8785
private void BuildTables()
8886
{
89-
var tables = WebApplicationExtensions.GetDbTablesForContext<TContext>().ToArray();
90-
InstantAPIsOptions.ITable[]? outTables;
91-
92-
// Add the Included tables
93-
if (_IncludedTables.Any())
94-
{
95-
outTables = tables.Where(t => _IncludedTables.Any(i => i.Name.Equals(t.Name, StringComparison.InvariantCultureIgnoreCase)))
96-
.Select(t => {
97-
var table = CreateTable(t.Name, new Uri(_IncludedTables.First(i => i.Name.Equals(t.Name, StringComparison.InvariantCultureIgnoreCase)).BaseUrl.ToString(), UriKind.Relative), typeof(TContext), typeof(DbSet<>).MakeGenericType(t.InstanceType), t.InstanceType);
98-
if (table != null)
99-
{
100-
table.ApiMethodsToGenerate = _IncludedTables.First(i => i.Name.Equals(t.Name, StringComparison.InvariantCultureIgnoreCase)).ApiMethodsToGenerate;
101-
}
102-
return table;
103-
})
104-
.Where(x => x != null).OfType<InstantAPIsOptions.ITable>()
105-
.ToArray();
106-
} else {
107-
outTables = tables
108-
.Select(t => CreateTable(t.Name, new Uri(DEFAULT_URI + t.Name, uriKind: UriKind.Relative), typeof(TContext), typeof(DbSet<>).MakeGenericType(t.InstanceType), t.InstanceType))
109-
.Where(x => x != null).OfType<InstantAPIsOptions.ITable>()
110-
.ToArray();
111-
}
112-
113-
// Exit now if no tables were excluded
114-
if (!_ExcludedTables.Any())
87+
if (!_tables.Any())
11588
{
116-
_Config.UnionWith(outTables);
117-
return;
89+
var discoveredTables = DiscoverTables();
90+
foreach (var discoveredTable in discoveredTables)
91+
{
92+
_tables.Add(discoveredTable);
93+
}
11894
}
11995

120-
// Remove the Excluded tables
121-
outTables = outTables.Where(t => !_ExcludedTables.Any(e => e.Equals(t.Name, StringComparison.InvariantCultureIgnoreCase))).ToArray();
122-
123-
if (outTables == null || !outTables.Any()) throw new ArgumentException("All tables were excluded from this configuration");
96+
_tables.RemoveWhere(t => _excludedTables.Any(e => t.Name.Equals(e, StringComparison.InvariantCultureIgnoreCase)));
12497

125-
_Config.UnionWith(outTables);
126-
127-
}
128-
129-
public static InstantAPIsOptions.ITable? CreateTable(string name, Uri baseUrl, Type contextType, Type setType, Type entityType)
130-
{
131-
var keyProperty = entityType.GetProperties().Where(x => "id".Equals(x.Name, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault();
132-
if (keyProperty == null) return null;
133-
134-
var genericMethod = typeof(InstantAPIsBuilder<>).MakeGenericType(contextType).GetMethod(nameof(CreateTableGeneric), BindingFlags.NonPublic | BindingFlags.Static)
135-
?? throw new Exception("Missing method");
136-
var concreteMethod = genericMethod.MakeGenericMethod(contextType, setType, entityType, keyProperty.PropertyType);
137-
138-
var entitySelector = CreateExpression(contextType, name, setType);
139-
var keySelector = CreateExpression(entityType, keyProperty.Name, keyProperty.PropertyType);
140-
return concreteMethod.Invoke(null, new object?[] { name, baseUrl, entitySelector, keySelector, null }) as InstantAPIsOptions.ITable;
98+
if (!_tables.Any()) throw new ArgumentException("All tables were excluded from this configuration");
14199
}
142100

143-
private static object CreateExpression(Type memberOwnerType, string property, Type returnType)
144-
{
145-
var parameterExpression = Expression.Parameter(memberOwnerType, "x");
146-
var propertyExpression = Expression.Property(parameterExpression, property);
147-
//var block = Expression.Block(propertyExpression, returnExpression);
148-
return Expression.Lambda(typeof(Func<,>).MakeGenericType(memberOwnerType, returnType), propertyExpression, parameterExpression);
149-
}
150-
151-
private static InstantAPIsOptions.ITable CreateTableGeneric<TContextStatic, TSet, TEntity, TKey>(string name, Uri baseUrl,
152-
Expression<Func<TContextStatic, TSet>> entitySelector, Expression<Func<TEntity, TKey>>? keySelector, Expression<Func<TEntity, TKey>>? orderBy)
153-
where TContextStatic : class
154-
where TSet : class
155-
where TEntity : class
156-
{
157-
return new InstantAPIsOptions.Table<TContextStatic, TSet, TEntity, TKey>(name, baseUrl, entitySelector,
158-
new InstantAPIsOptions.TableOptions<TEntity, TKey>()
159-
{
160-
KeySelector = keySelector,
161-
OrderBy = orderBy
162-
});
163-
}
164101
#endregion
165102

166-
internal HashSet<InstantAPIsOptions.ITable> Build()
103+
internal IEnumerable<InstantAPIsOptions.ITable> Build()
167104
{
168-
169105
BuildTables();
170106

171-
return _Config;
107+
return _tables;
172108
}
173109

174-
}
110+
}

InstantAPIs/InstantAPIsOptions.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,12 @@ public enum EnableSwagger
1212

1313
public class InstantAPIsOptions
1414
{
15+
public Uri DefaultUri = new Uri("/api", UriKind.Relative);
1516

16-
public EnableSwagger? EnableSwagger { get; set; }
17-
public Action<SwaggerGenOptions>? Swagger { get; set; }
17+
public EnableSwagger? EnableSwagger { get; set; }
18+
public Action<SwaggerGenOptions>? Swagger { get; set; }
19+
20+
public IEnumerable<ITable> Tables { get; internal set; } = new HashSet<ITable>();
1821

1922
internal class Table<TContext, TSet, TEntity, TKey>
2023
: ITable
Lines changed: 40 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,45 @@
1-
using Microsoft.Extensions.DependencyInjection;
1+
using InstantAPIs.Repositories;
22

3-
namespace InstantAPIs;
3+
namespace Microsoft.Extensions.DependencyInjection;
44

55
public static class InstantAPIsServiceCollectionExtensions
66
{
7-
public static IServiceCollection AddInstantAPIs(this IServiceCollection services, Action<InstantAPIsOptions>? setupAction = null)
8-
{
9-
var options = new InstantAPIsOptions();
10-
11-
// Get the service options
12-
setupAction?.Invoke(options);
13-
14-
if (options.EnableSwagger == null)
15-
{
16-
options.EnableSwagger = EnableSwagger.DevelopmentOnly;
17-
}
18-
19-
// Add and configure Swagger services if it is enabled
20-
if (options.EnableSwagger != EnableSwagger.None)
21-
{
22-
services.AddEndpointsApiExplorer();
23-
services.AddSwaggerGen(options.Swagger);
24-
}
25-
26-
// Register the required options so that it can be accessed by InstantAPIs middleware
27-
services.Configure<InstantAPIsOptions>(config =>
28-
{
29-
config.EnableSwagger = options.EnableSwagger;
30-
});
31-
32-
return services;
33-
}
7+
public static IServiceCollection AddInstantAPIs(this IServiceCollection services, Action<InstantAPIsOptions>? setupAction = null)
8+
{
9+
var options = new InstantAPIsOptions();
10+
11+
// Get the service options
12+
setupAction?.Invoke(options);
13+
14+
if (options.EnableSwagger == null)
15+
{
16+
options.EnableSwagger = EnableSwagger.DevelopmentOnly;
17+
}
18+
19+
// Add and configure Swagger services if it is enabled
20+
if (options.EnableSwagger != EnableSwagger.None)
21+
{
22+
services.AddEndpointsApiExplorer();
23+
services.AddSwaggerGen(options.Swagger);
24+
}
25+
26+
// Register the required options so that it can be accessed by InstantAPIs middleware
27+
services.Configure<InstantAPIsOptions>(config =>
28+
{
29+
config.EnableSwagger = options.EnableSwagger;
30+
});
31+
32+
services.AddSingleton(typeof(IRepositoryHelperFactory<,,,>), typeof(RepositoryHelperFactory<,,,>));
33+
services.AddSingleton(typeof(IContextHelper<>), typeof(ContextHelper<>));
34+
35+
// ef core specific
36+
services.AddSingleton<IRepositoryHelperFactory, InstantAPIs.Repositories.EntityFrameworkCore.RepositoryHelperFactory>();
37+
services.AddSingleton<IContextHelper, InstantAPIs.Repositories.EntityFrameworkCore.ContextHelper>();
38+
39+
// json specific
40+
services.AddSingleton<IRepositoryHelperFactory, InstantAPIs.Repositories.Json.RepositoryHelperFactory>();
41+
services.AddSingleton<IContextHelper, InstantAPIs.Repositories.Json.ContextHelper>();
42+
43+
return services;
44+
}
3445
}

0 commit comments

Comments
 (0)