diff --git a/README.md b/README.md
index 6d92617..7cde786 100644
--- a/README.md
+++ b/README.md
@@ -63,6 +63,22 @@ The easiest way to install EntityDock in your project is to install the latest E
It's possible that more packages may be added in the future.
+
+
+You can install these package following the next example:
+
+```bash
+Install-Package EntityDock.Lib.Auto
+```
+
+or using dotnet CLI:
+
+```bash
+dotnet add package EntityDock.Lib.Auto
+```
+
+
+
# Key questions
**What's mean "generate controller"?** Yes, without writing a line of code or declaring a class you can have API Controllers base on ASP.NET Core MVC from declared entities. The code required for this is as follows:
@@ -76,6 +92,7 @@ When you are setting up your MVC options in ASP.NET Core, you must call this met
```c#
[SetRouteAttibute("data/students")]
public class StudentEntity{
+
public uint Id {get;set;}
public string Name {get;set;}
@@ -88,11 +105,9 @@ public class StudentEntity{
}
```
-Then you will have a complete API Rest about this entity with full methods, Crud, search, filters, sort and more.
-
-**How works the `AutoDbContext`?** It's simple, this is a class that derived from `DbContext` in Entity Framework, then using this class, you can create a context from external assemblies or types collections that will has these types as entities and this context can be used like other any context of Entity Framework. Using this way you cannot setup via `ModelBuilder` API fluent methods inside context class, you just have conventions and annotations for `AutoDbContext`. This is a natural limitations 'cause its job consists of including different entities without declare specific `DbContext`.
-
+Then you will have a complete API Rest about this entity with full methods, Crud, search, filters, sort and more. Of course you should care about your DB connections and migrations if you are using relational database. This package is completely compatible with Entity Framework Core.
+**How works the `AutoDbContext`?** It's simple, this is a class that derived from `DbContext` in Entity Framework Core, then using this class, you can create a context from external assemblies or types collections that will has these types as entities and this context can be used like other any context of Entity Framework. Using this way you cannot setup via `ModelBuilder` API fluent methods inside context class, you just have conventions and annotations for `AutoDbContext`. This is a natural limitations 'cause its job consists of including different entities without declare specific `DbContext`.
## Contributing
diff --git a/src/Libs/Auto/AutoApiOption.cs b/src/Libs/Auto/AutoApiOption.cs
new file mode 100644
index 0000000..856c4ff
--- /dev/null
+++ b/src/Libs/Auto/AutoApiOption.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace EntityDock.Lib.Auto
+{
+ public class AutoApiOption
+ {
+ public bool ApiUsageService { get; set; } = false;
+
+ public bool SchemaShareEnabled { get; set; } = false;
+ }
+}
diff --git a/src/Libs/Auto/AutoDbExtensions.cs b/src/Libs/Auto/AutoDbExtensions.cs
new file mode 100644
index 0000000..ecd2e3f
--- /dev/null
+++ b/src/Libs/Auto/AutoDbExtensions.cs
@@ -0,0 +1,133 @@
+using EntityDock.Lib.Base;
+using Microsoft.Extensions.DependencyInjection;
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using EntityDock.Lib.Auto;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+using System.Linq;
+
+namespace Microsoft.Extensions.DependencyInjection
+{
+ public static class AutoDbExtensions
+ {
+ ///
+ /// Add auto controllers features in services collections
+ ///
+ ///
+ ///
+ public static void AddDataControllers(this IMvcCoreBuilder services, Type[] types, AutoApiOption options = null)
+ {
+ // feature to add controller frome ntity types
+ services.PartManager.FeatureProviders.Add(new ControllerMakerFeatureProvider(types, options));
+
+ // convention to use in routes
+ services.AddMvcOptions(x => x.Conventions.Add(new GenericControllerFeatureConvention()));
+ }
+
+ ///
+ /// Add auto controllers features in services collections
+ ///
+ ///
+ ///
+ public static void AddDataControllers(this IMvcBuilder services, Type[] types, AutoApiOption options = null)
+ {
+ // feature to add controller frome ntity types
+ services.PartManager.FeatureProviders.Add(new ControllerMakerFeatureProvider(types, options));
+
+ // convention to use in routes
+ services.AddMvcOptions(x => x.Conventions.Add(new GenericControllerFeatureConvention()));
+ }
+
+ ///
+ /// Add auto controllers features in services collections
+ ///
+ ///
+ ///
+ public static void AddDataControllers(this IMvcCoreBuilder services,
+ Type dbContext,
+ bool deepScan = false,
+ AutoApiOption options = null)
+ {
+ if (!dbContext.IsAssignableFrom(typeof(DbContext)))
+ {
+ throw new Exception();
+ }
+
+ var types = dbContext.GetProperties()
+ .Where(x => x.PropertyType.Name.Equals(typeof(DbSet<>).Name))
+ .Select(x => x.PropertyType.GenericTypeArguments[0])
+ .ToArray();
+
+ // feature to add controller frome ntity types
+ services.PartManager.FeatureProviders.Add(new ControllerMakerFeatureProvider(types, options));
+
+ // convention to use in routes
+ services.AddMvcOptions(x => x.Conventions.Add(new GenericControllerFeatureConvention()));
+ }
+
+ ///
+ /// Add auto controllers features in services collections
+ ///
+ ///
+ ///
+ public static void AddDataControllers(this IMvcBuilder services,
+ Type dbContext,
+ bool deepScan = false,
+ AutoApiOption options = null)
+ {
+ if (services is null)
+ {
+ throw new ArgumentNullException(nameof(services));
+ }
+
+ if (dbContext is null)
+ {
+ throw new ArgumentNullException(nameof(dbContext));
+ }
+
+ var types = dbContext.GetProperties()
+ .Where(x => x.PropertyType.Name.Equals(typeof(DbSet<>).Name))
+ .Select(x => x.PropertyType.GenericTypeArguments[0])
+ .ToArray();
+
+ // feature to add controller frome ntity types
+ services.PartManager.FeatureProviders.Add(new ControllerMakerFeatureProvider(types, options));
+
+ // convention to use in routes
+ services.AddMvcOptions(x => x.Conventions.Add(new GenericControllerFeatureConvention()));
+ }
+
+ ///
+ /// Add auto controllers features in services collections using a passed type argument.
+ ///
+ ///
+ ///
+ ///
+ public static void AddDataControllers(this IMvcCoreBuilder services, bool deepScan = false)
+ {
+ services.AddDataControllers(typeof(TContext), deepScan);
+ }
+
+ ///
+ /// Add auto controllers features in services collections using a passed type argument.
+ ///
+ ///
+ ///
+ ///
+ public static void AddDataControllers(this IMvcBuilder services, bool deepScan = false)
+ {
+ services.AddDataControllers(typeof(TContext), deepScan);
+ }
+
+ ///
+ /// Add auto controllers features in services collections
+ ///
+ ///
+ public static void AddFilterTriggers(this IMvcCoreBuilder services, Type[] types)
+ {
+ services.AddMvcOptions(opt => opt.Filters.Add());
+ }
+ }
+}
diff --git a/src/Libs/Auto/ControllerMakerFeatureProvider.cs b/src/Libs/Auto/ControllerMakerFeatureProvider.cs
index cb51f8c..4be1874 100644
--- a/src/Libs/Auto/ControllerMakerFeatureProvider.cs
+++ b/src/Libs/Auto/ControllerMakerFeatureProvider.cs
@@ -10,7 +10,6 @@
namespace EntityDock.Lib.Auto
{
-
///
/// Create controller by passed types as controller
///
@@ -25,7 +24,6 @@ public static ControllerMakerFeatureProvider FromAssembly(Assembly assembly)
{
// filter and take an array of the candidates
return new ControllerMakerFeatureProvider(types: assembly.GetExportedTypes()
- .Where(t => t.IsEntity())
.Where(t => t.IsDefined(typeof(SetRouteAttibute)))
.ToArray()
);
@@ -35,15 +33,22 @@ public static ControllerMakerFeatureProvider FromAssembly(Assembly assembly)
/// Require entry types for mapping
///
///
- public ControllerMakerFeatureProvider(Type[] types)
+ public ControllerMakerFeatureProvider(Type[] types, AutoApiOption options = default)
{
- TargetTypes = types;
+ if (options is null)
+ {
+ options = new AutoApiOption();
+ }
+
+ TargetTypes = types ?? throw new ArgumentNullException(nameof(types));
+ Options = options;
}
///
/// All passed types
///
public Type[] TargetTypes { get; }
+ public AutoApiOption Options { get; }
///
/// Populate all controller created from passed types
@@ -57,7 +62,7 @@ public void PopulateFeature(IEnumerable parts, ControllerFeatur
{
// push new controller from route
feature.Controllers.Add(item: GetCandidateController(route)
- .MakeGenericType(route.Model)
+ .MakeGenericType(route.Model, HelpersExtensions.FindKeyType(route.Model))
.GetTypeInfo()
);
}
@@ -71,9 +76,10 @@ public void PopulateFeature(IEnumerable parts, ControllerFeatur
private Type GetCandidateController(UnitRoute item)
=> item.ModelType switch
{
- ModelType.FullyFeatures => typeof(FullyFeatureController<,>),
- ModelType.Record => typeof(RecordController<,>),
- //ModelType.Record => typeof(),
+ ModelType.FullyFeatures when(Options.ApiUsageService) => typeof(FullyFeatureController<,>),
+ ModelType.Record when(Options.ApiUsageService) => typeof(RecordController<,>),
+ ModelType.FullyFeatures => typeof(RepoFullyFeatureController<,>),
+ ModelType.Record => typeof(RepoRecordController<,>),
_ => null
};
@@ -87,17 +93,31 @@ private IEnumerable GetRoutes()
{
var attr = target.GetCustomAttribute();
- // make an route from attributes specifications
- return new UnitRoute {
- Model = target,
- ModelType = attr.Usage switch
+ // if has attribute
+ if (attr != null)
+ {
+ // make an route from attributes specifications
+ return new UnitRoute
+ {
+ Model = target,
+ ModelType = attr.Usage switch
+ {
+ EntityUsage.Readonly => ModelType.Readonly,
+ EntityUsage.Record => ModelType.Record,
+ EntityUsage.FullyUsage => ModelType.FullyFeatures,
+ _ => throw new InvalidOperationException()
+ }
+ };
+ }
+ else
+ {
+ // from static definition
+ return new UnitRoute
{
- EntityUsage.Readonly => ModelType.Readonly,
- EntityUsage.Record => ModelType.Record,
- EntityUsage.FullyUsage => ModelType.FullyFeatures,
- _ => throw new InvalidOperationException()
- }
- };
+ Model = target,
+ ModelType = ModelType.FullyFeatures
+ };
+ }
});
}
}
diff --git a/src/Libs/Auto/Controllers/FullyFeatureController.cs b/src/Libs/Auto/Controllers/FullyFeatureController.cs
index 24fa910..71cbca8 100644
--- a/src/Libs/Auto/Controllers/FullyFeatureController.cs
+++ b/src/Libs/Auto/Controllers/FullyFeatureController.cs
@@ -1,45 +1,30 @@
using System;
using System.Linq;
-
using System.Threading.Tasks;
using EntityDock.Persistence;
using Microsoft.AspNetCore.Mvc;
using EntityDock.Entities.Base;
-using System.Collections.Generic;
using EntityDock.Extensions.Query;
using Microsoft.EntityFrameworkCore;
using Swashbuckle.AspNetCore.Annotations;
using System.Linq.Dynamic.Core;
-using EntityDock;
namespace EntityDock.Lib.Auto.Controllers
{
///
/// Markets crud example with functional Api Systems
///
- [ApiController]
- public class FullyFeatureController : ControllerBase
+ public class FullyFeatureController : OperationsController
where T: AggregateRoot
{
///
/// Require basic data service
///
///
- public FullyFeatureController(DataService service)
+ public FullyFeatureController(DataService service) : base(service)
{
- if (service is null)
- {
- throw new ArgumentNullException(nameof(service));
- }
-
- DataService = service;
}
- ///
- /// Reference of the active service for this entity
- ///
- public DataService DataService { get; set; }
-
///
/// Devuelve un listado de todos los registros de datos
///
@@ -55,7 +40,7 @@ public FullyFeatureController(DataService service)
///
///
[HttpGet]
- public async Task QueryMarkets(
+ public async Task Query(
[SwaggerParameter(" Array with rules to filter"), FromQuery] string[] filter,
[SwaggerParameter(" Array with rules to allow select other records"), FromQuery] string[] or,
[SwaggerParameter(" Array with rules to include relations"), FromQuery] string[] join,
@@ -201,7 +186,204 @@ public async Task GetCountAsync(
///
///
[HttpDelete]
+#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
+ public async Task DeleteQueryResultAsync(
+#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
+ [SwaggerParameter(" Array with rules to filter"), FromQuery] string[] filter,
+ [SwaggerParameter(" Array with rules to allow select other records"), FromQuery] string[] or,
+ [SwaggerParameter(" Array with rules to include relations"), FromQuery] string[] join,
+ [SwaggerParameter(" Array with rules to sort"), FromQuery] string[] select,
+ [SwaggerParameter(" Array with rules to sort"), FromQuery] string[] sort,
+ [SwaggerParameter(" Page that will show")] int page,
+ [SwaggerParameter(" Limit in query or page size if the {page} > 0")] int limit,
+ [SwaggerParameter(" Simple offset parameters to skip records")] int offset,
+ [SwaggerParameter(" Search keywords")] string search,
+ [SwaggerParameter(" Search method")] string searchMethod,
+ [SwaggerParameter(" Case sensitive for Search")] bool caseSensitive)
+ {
+ return null;
+ }
+ }
+
+ ///
+ /// Markets crud example with functional Api Systems
+ ///
+ public class RepoFullyFeatureController : RepoOperationsController
+ where T : AggregateRoot
+ {
+ ///
+ /// Require basic data service
+ ///
+ ///
+ public RepoFullyFeatureController(IRepository service) : base(service)
+ {
+ }
+
+ ///
+ /// Devuelve un listado de todos los registros de datos
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ [HttpGet]
+ public async Task Query(
+ [SwaggerParameter(" Array with rules to filter"), FromQuery] string[] filter,
+ [SwaggerParameter(" Array with rules to allow select other records"), FromQuery] string[] or,
+ [SwaggerParameter(" Array with rules to include relations"), FromQuery] string[] join,
+ [SwaggerParameter(" Array with rules to sort"), FromQuery] string[] select,
+ [SwaggerParameter(" Array with rules to sort"), FromQuery] string[] sort,
+ [SwaggerParameter(" Page that will show")] int page,
+ [SwaggerParameter(" Limit in query or page size if the {page} > 0")] int limit,
+ [SwaggerParameter(" Simple offset parameters to skip records")] int offset,
+ [SwaggerParameter(" Search keywords")] string search,
+ [SwaggerParameter(" Search method")] string searchMethod,
+ [SwaggerParameter(" Case sensitive for Search")] bool caseSensitive,
+ [SwaggerParameter(" Cache enable")] bool cache)
+ {
+ // full initialize
+ var queryModel = new FundamentalQueryModel(filter, or, join,
+ select: select, sorts: sort, page: page, limit: limit,
+ offset: offset, search: search, searchMethod: searchMethod,
+ caseSensitive: caseSensitive, cache: cache);
+
+ // apply filter base on model parameters
+ var query = Repo.Get()
+ .ApplyFilter(queryModel);
+
+ // this variable is declared to save the record number
+ int count = 0;
+
+ // check
+ if (queryModel.Page > 0)
+ {
+ var pageNumber = queryModel.Page;
+ var pageSize = queryModel.Limit;
+ pageNumber = pageNumber == 0 ? 1 : pageNumber;
+ pageSize = pageSize == 0 ? 10 : pageSize;
+
+ // is necesary do this before apply pagination but with filter applied
+ // because the filters determine the total record if has filter in query
+ count = await query.CountAsync();
+ query = query.Skip((pageNumber - 1) * pageSize).Take(pageSize);
+ }
+ else
+ {
+ if (queryModel.Limit > 0)
+ {
+ query = query.Take(queryModel.Limit);
+ }
+
+ if (queryModel.Limit > 0)
+ {
+ query = query.Skip(queryModel.Offset);
+ }
+ }
+
+ // container to store results
+ object result;
+
+ // define the result base on pagination request
+ if (queryModel.Select is not null && queryModel.Select.Length > 0)
+ {
+ result = await query.SelectFields(queryModel.Select)
+ .ToDynamicListAsync();
+ }
+ else
+ {
+ result = await query.ToListAsync();
+ }
+
+ // this variable is necesary to store paginated result or simple result
+ var finalResult = page > 0 ? new PaginatedResult