Skip to content

Commit

Permalink
Enabling multiple data providers
Browse files Browse the repository at this point in the history
- added an interface with factory
- included ef core provider
- included json provider
- update the test programs to utilize the repository approach
  • Loading branch information
Dries Verbeke committed Jul 22, 2022
1 parent 3ebe5d5 commit c614f7a
Show file tree
Hide file tree
Showing 26 changed files with 674 additions and 618 deletions.
150 changes: 43 additions & 107 deletions InstantAPIs/InstantAPIsBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
using System.Linq.Expressions;
using System.Reflection;
using InstantAPIs.Repositories;
using System.Linq.Expressions;

namespace Microsoft.AspNetCore.Builder;

public class InstantAPIsBuilder<TContext>
where TContext : DbContext
public class InstantAPIsBuilder<TContext>
where TContext : class
{
private readonly InstantAPIsOptions _instantApiOptions;
private readonly IContextHelper<TContext> _contextFactory;
private readonly HashSet<InstantAPIsOptions.ITable> _tables = new HashSet<InstantAPIsOptions.ITable>();
private readonly IList<string> _excludedTables = new List<string>();

private HashSet<InstantAPIsOptions.ITable> _Config = new();
private Type _ContextType = typeof(TContext);
private TContext _TheContext;
private readonly HashSet<InstantAPIsOptions.ITable> _IncludedTables = new();
private readonly List<string> _ExcludedTables = new();
private const string DEFAULT_URI = "/api/";
public InstantAPIsBuilder(InstantAPIsOptions instantApiOptions, IContextHelper<TContext> contextFactory)
{
_instantApiOptions = instantApiOptions;
_contextFactory = contextFactory;
}

public InstantAPIsBuilder(TContext theContext)
private IEnumerable<InstantAPIsOptions.ITable> DiscoverTables()
{
this._TheContext = theContext;
return _contextFactory != null
? _contextFactory.DiscoverFromContext(_instantApiOptions.DefaultUri)
: Array.Empty<InstantAPIsOptions.ITable>();
}

#region Table Inclusion/Exclusion
Expand All @@ -27,14 +32,13 @@ public InstantAPIsBuilder(TContext theContext)
/// <param name="setSelector">Select the EntityFramework DbSet to include - Required</param>
/// <param name="methodsToGenerate">A flags enumerable indicating the methods to generate. By default ALL are generated</param>
/// <returns>Configuration builder with this configuration applied</returns>
public InstantAPIsBuilder<TContext> IncludeTable<TSet, TEntity, TKey>(Expression<Func<TContext, TSet>> setSelector,
InstantAPIsOptions.TableOptions<TEntity, TKey> config, ApiMethodsToGenerate methodsToGenerate = ApiMethodsToGenerate.All, string baseUrl = "")
where TSet : DbSet<TEntity>
public InstantAPIsBuilder<TContext> IncludeTable<TSet, TEntity, TKey>(Expression<Func<TContext, TSet>> setSelector,
InstantAPIsOptions.TableOptions<TEntity, TKey> config, ApiMethodsToGenerate methodsToGenerate = ApiMethodsToGenerate.All,
string baseUrl = "")
where TSet : class
where TEntity : class
{

var theSetType = setSelector.Compile()(_TheContext).GetType().BaseType;
var property = _ContextType.GetProperties().First(p => p.PropertyType == theSetType);
var propertyName = _contextFactory.NameTable(setSelector);

if (!string.IsNullOrEmpty(baseUrl))
{
Expand All @@ -50,17 +54,16 @@ public InstantAPIsBuilder<TContext> IncludeTable<TSet, TEntity, TKey>(Expression
}
else
{
baseUrl = string.Concat(DEFAULT_URI, property.Name);
baseUrl = string.Concat(_instantApiOptions.DefaultUri.ToString(), "/", propertyName);
}

var tableApiMapping = new InstantAPIsOptions.Table<TContext, TSet, TEntity, TKey>(property.Name, new Uri(baseUrl, UriKind.Relative), setSelector, config)
{
ApiMethodsToGenerate = methodsToGenerate
var tableApiMapping = new InstantAPIsOptions.Table<TContext, TSet, TEntity, TKey>(propertyName, new Uri(baseUrl, UriKind.Relative), setSelector, config)
{
ApiMethodsToGenerate = methodsToGenerate
};
_IncludedTables.Add(tableApiMapping);

if (_ExcludedTables.Contains(tableApiMapping.Name)) _ExcludedTables.Remove(tableApiMapping.Name);
_IncludedTables.Add(tableApiMapping);
_tables.RemoveWhere(x => x.Name == tableApiMapping.Name);
_tables.Add(tableApiMapping);

return this;

Expand All @@ -69,106 +72,39 @@ public InstantAPIsBuilder<TContext> IncludeTable<TSet, TEntity, TKey>(Expression
/// <summary>
/// Exclude individual tables from the API generation. Exclusion takes priority over inclusion
/// </summary>
/// <param name="entitySelector">Select the entity to exclude from generation</param>
/// <param name="setSelector">Select the entity to exclude from generation</param>
/// <returns>Configuration builder with this configuraiton applied</returns>
public InstantAPIsBuilder<TContext> ExcludeTable<T>(Func<TContext, DbSet<T>> entitySelector) where T : class
public InstantAPIsBuilder<TContext> ExcludeTable<TSet>(Expression<Func<TContext, TSet>> setSelector) where TSet : class
{

var theSetType = entitySelector(_TheContext).GetType().BaseType;
var property = _ContextType.GetProperties().First(p => p.PropertyType == theSetType);

if (_IncludedTables.Select(t => t.Name).Contains(property.Name)) _IncludedTables.Remove(_IncludedTables.First(t => t.Name == property.Name));
_ExcludedTables.Add(property.Name);
var propertyName = _contextFactory.NameTable(setSelector);
_excludedTables.Add(propertyName);

return this;

}

private void BuildTables()
{
var tables = WebApplicationExtensions.GetDbTablesForContext<TContext>().ToArray();
InstantAPIsOptions.ITable[]? outTables;

// Add the Included tables
if (_IncludedTables.Any())
{
outTables = tables.Where(t => _IncludedTables.Any(i => i.Name.Equals(t.Name, StringComparison.InvariantCultureIgnoreCase)))
.Select(t => {
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);
if (table != null)
{
table.ApiMethodsToGenerate = _IncludedTables.First(i => i.Name.Equals(t.Name, StringComparison.InvariantCultureIgnoreCase)).ApiMethodsToGenerate;
}
return table;
})
.Where(x => x != null).OfType<InstantAPIsOptions.ITable>()
.ToArray();
} else {
outTables = tables
.Select(t => CreateTable(t.Name, new Uri(DEFAULT_URI + t.Name, uriKind: UriKind.Relative), typeof(TContext), typeof(DbSet<>).MakeGenericType(t.InstanceType), t.InstanceType))
.Where(x => x != null).OfType<InstantAPIsOptions.ITable>()
.ToArray();
}

// Exit now if no tables were excluded
if (!_ExcludedTables.Any())
if (!_tables.Any())
{
_Config.UnionWith(outTables);
return;
var discoveredTables = DiscoverTables();
foreach (var discoveredTable in discoveredTables)
{
_tables.Add(discoveredTable);
}
}

// Remove the Excluded tables
outTables = outTables.Where(t => !_ExcludedTables.Any(e => e.Equals(t.Name, StringComparison.InvariantCultureIgnoreCase))).ToArray();

if (outTables == null || !outTables.Any()) throw new ArgumentException("All tables were excluded from this configuration");
_tables.RemoveWhere(t => _excludedTables.Any(e => t.Name.Equals(e, StringComparison.InvariantCultureIgnoreCase)));

_Config.UnionWith(outTables);

}

public static InstantAPIsOptions.ITable? CreateTable(string name, Uri baseUrl, Type contextType, Type setType, Type entityType)
{
var keyProperty = entityType.GetProperties().Where(x => "id".Equals(x.Name, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault();
if (keyProperty == null) return null;

var genericMethod = typeof(InstantAPIsBuilder<>).MakeGenericType(contextType).GetMethod(nameof(CreateTableGeneric), BindingFlags.NonPublic | BindingFlags.Static)
?? throw new Exception("Missing method");
var concreteMethod = genericMethod.MakeGenericMethod(contextType, setType, entityType, keyProperty.PropertyType);

var entitySelector = CreateExpression(contextType, name, setType);
var keySelector = CreateExpression(entityType, keyProperty.Name, keyProperty.PropertyType);
return concreteMethod.Invoke(null, new object?[] { name, baseUrl, entitySelector, keySelector, null }) as InstantAPIsOptions.ITable;
if (!_tables.Any()) throw new ArgumentException("All tables were excluded from this configuration");
}

private static object CreateExpression(Type memberOwnerType, string property, Type returnType)
{
var parameterExpression = Expression.Parameter(memberOwnerType, "x");
var propertyExpression = Expression.Property(parameterExpression, property);
//var block = Expression.Block(propertyExpression, returnExpression);
return Expression.Lambda(typeof(Func<,>).MakeGenericType(memberOwnerType, returnType), propertyExpression, parameterExpression);
}

private static InstantAPIsOptions.ITable CreateTableGeneric<TContextStatic, TSet, TEntity, TKey>(string name, Uri baseUrl,
Expression<Func<TContextStatic, TSet>> entitySelector, Expression<Func<TEntity, TKey>>? keySelector, Expression<Func<TEntity, TKey>>? orderBy)
where TContextStatic : class
where TSet : class
where TEntity : class
{
return new InstantAPIsOptions.Table<TContextStatic, TSet, TEntity, TKey>(name, baseUrl, entitySelector,
new InstantAPIsOptions.TableOptions<TEntity, TKey>()
{
KeySelector = keySelector,
OrderBy = orderBy
});
}
#endregion

internal HashSet<InstantAPIsOptions.ITable> Build()
internal IEnumerable<InstantAPIsOptions.ITable> Build()
{

BuildTables();

return _Config;
return _tables;
}

}
}
7 changes: 5 additions & 2 deletions InstantAPIs/InstantAPIsOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@ public enum EnableSwagger

public class InstantAPIsOptions
{
public Uri DefaultUri = new Uri("/api", UriKind.Relative);

public EnableSwagger? EnableSwagger { get; set; }
public Action<SwaggerGenOptions>? Swagger { get; set; }
public EnableSwagger? EnableSwagger { get; set; }
public Action<SwaggerGenOptions>? Swagger { get; set; }

public IEnumerable<ITable> Tables { get; internal set; } = new HashSet<ITable>();

internal class Table<TContext, TSet, TEntity, TKey>
: ITable
Expand Down
69 changes: 40 additions & 29 deletions InstantAPIs/InstantAPIsServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,34 +1,45 @@
using Microsoft.Extensions.DependencyInjection;
using InstantAPIs.Repositories;

namespace InstantAPIs;
namespace Microsoft.Extensions.DependencyInjection;

public static class InstantAPIsServiceCollectionExtensions
{
public static IServiceCollection AddInstantAPIs(this IServiceCollection services, Action<InstantAPIsOptions>? setupAction = null)
{
var options = new InstantAPIsOptions();

// Get the service options
setupAction?.Invoke(options);

if (options.EnableSwagger == null)
{
options.EnableSwagger = EnableSwagger.DevelopmentOnly;
}

// Add and configure Swagger services if it is enabled
if (options.EnableSwagger != EnableSwagger.None)
{
services.AddEndpointsApiExplorer();
services.AddSwaggerGen(options.Swagger);
}

// Register the required options so that it can be accessed by InstantAPIs middleware
services.Configure<InstantAPIsOptions>(config =>
{
config.EnableSwagger = options.EnableSwagger;
});

return services;
}
public static IServiceCollection AddInstantAPIs(this IServiceCollection services, Action<InstantAPIsOptions>? setupAction = null)
{
var options = new InstantAPIsOptions();

// Get the service options
setupAction?.Invoke(options);

if (options.EnableSwagger == null)
{
options.EnableSwagger = EnableSwagger.DevelopmentOnly;
}

// Add and configure Swagger services if it is enabled
if (options.EnableSwagger != EnableSwagger.None)
{
services.AddEndpointsApiExplorer();
services.AddSwaggerGen(options.Swagger);
}

// Register the required options so that it can be accessed by InstantAPIs middleware
services.Configure<InstantAPIsOptions>(config =>
{
config.EnableSwagger = options.EnableSwagger;
});

services.AddSingleton(typeof(IRepositoryHelperFactory<,,,>), typeof(RepositoryHelperFactory<,,,>));
services.AddSingleton(typeof(IContextHelper<>), typeof(ContextHelper<>));

// ef core specific
services.AddSingleton<IRepositoryHelperFactory, InstantAPIs.Repositories.EntityFrameworkCore.RepositoryHelperFactory>();
services.AddSingleton<IContextHelper, InstantAPIs.Repositories.EntityFrameworkCore.ContextHelper>();

// json specific
services.AddSingleton<IRepositoryHelperFactory, InstantAPIs.Repositories.Json.RepositoryHelperFactory>();
services.AddSingleton<IContextHelper, InstantAPIs.Repositories.Json.ContextHelper>();

return services;
}
}
Loading

0 comments on commit c614f7a

Please sign in to comment.