From 897b801cd4c2a3609760481abde58b30fcaa1aaa Mon Sep 17 00:00:00 2001 From: Tobias Klika Date: Fri, 22 May 2020 21:19:49 +0200 Subject: [PATCH] part of the extensibility works now --- zero.Core/Api/ApplicationsApi.cs | 21 ++- zero.Core/ApiControllerFeatureProvider.cs | 31 ----- .../Entities/Applications/Application.cs | 1 + zero.Core/EntityMap.cs | 66 +++++++++ .../Extensions/DocumentStoreExtensions.cs | 2 +- zero.Core/ServiceCollectionExtensions.cs | 64 ++++++++- zero.Core/zero.Core.csproj | 1 + zero.Debug/Controllers/TestController.cs | 35 ++++- zero.Debug/MyApplication.cs | 16 ++- zero.Debug/Startup.cs | 3 + zero.Debug/TestData/TestPlugin.cs | 5 + .../App/pages/settings/application.vue | 2 +- zero.Web/ApiControllerFeatureProvider.cs | 40 ++++-- zero.Web/ApplicationRenderer.cs | 19 +++ .../Controllers/Applications2Controller.cs | 73 ---------- .../Controllers/ApplicationsController.cs | 128 +++++++----------- zero.Web/Controllers/BackofficeController.cs | 4 +- zero.Web/ZeroBuilder.cs | 15 +- zero.sln | 5 - 19 files changed, 310 insertions(+), 221 deletions(-) delete mode 100644 zero.Core/ApiControllerFeatureProvider.cs create mode 100644 zero.Core/EntityMap.cs create mode 100644 zero.Web/ApplicationRenderer.cs delete mode 100644 zero.Web/Controllers/Applications2Controller.cs diff --git a/zero.Core/Api/ApplicationsApi.cs b/zero.Core/Api/ApplicationsApi.cs index a712ece3..0cff3168 100644 --- a/zero.Core/Api/ApplicationsApi.cs +++ b/zero.Core/Api/ApplicationsApi.cs @@ -1,4 +1,5 @@ -using Raven.Client.Documents; +using FluentValidation; +using Raven.Client.Documents; using Raven.Client.Documents.Session; using System.Collections.Generic; using System.Threading.Tasks; @@ -8,17 +9,15 @@ using zero.Core.Validation; namespace zero.Core.Api { - //public class ApplicationsApi : ApplicationsApi - //{ - // public ApplicationsApi(IBackofficeStore store) : base(store) { } - //} - - //public interface IApplicationsApi : IApplicationsApi { } - - public class ApplicationsApi : BackofficeApi, IApplicationsApi where T : IApplication { - public ApplicationsApi(IBackofficeStore store) : base(store) { } + IValidator Validator; + + + public ApplicationsApi(IBackofficeStore store, IValidator validator) : base(store) + { + Validator = validator; + } /// @@ -57,7 +56,7 @@ namespace zero.Core.Api /// public async Task> Save(T model) { - return await Save(model, new ApplicationValidator()); + return await Save(model, Validator); } diff --git a/zero.Core/ApiControllerFeatureProvider.cs b/zero.Core/ApiControllerFeatureProvider.cs deleted file mode 100644 index 3ee6c494..00000000 --- a/zero.Core/ApiControllerFeatureProvider.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Microsoft.AspNetCore.Mvc.ApplicationParts; -using Microsoft.AspNetCore.Mvc.Controllers; -using System; -using System.Collections.Generic; -using System.Reflection; -using System.Text; - -namespace zero.Core -{ - public class ApiControllerFeatureProvider : IApplicationFeatureProvider - { - public void PopulateFeature(IEnumerable parts, ControllerFeature feature) - { - var ctrls = feature.Controllers; - - //var type = typeof(ApplicationsController) - - //var controllerType = typeof(GenericController<>).MakeGenericType(entityType.AsType()).GetTypeInfo(); - - //feature.Controllers.Add() - //var typeName = entityType.Name + "Controller"; - - //// Check to see if there is a "real" controller for this class - //if (!feature.Controllers.Any(t => t.Name == typeName)) - //{ - // // Create a generic controller for this type - // feature.Controllers.Add(controllerType); - //} - } - } -} diff --git a/zero.Core/Entities/Applications/Application.cs b/zero.Core/Entities/Applications/Application.cs index 7ab63e8b..80d0cd90 100644 --- a/zero.Core/Entities/Applications/Application.cs +++ b/zero.Core/Entities/Applications/Application.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using zero.Core.Attributes; namespace zero.Core.Entities { diff --git a/zero.Core/EntityMap.cs b/zero.Core/EntityMap.cs new file mode 100644 index 00000000..b1603502 --- /dev/null +++ b/zero.Core/EntityMap.cs @@ -0,0 +1,66 @@ +using FluentValidation; +using System; +using System.Collections.Generic; +using System.Text; +using zero.Core.Renderer; + +namespace zero.Core +{ + public static class EntityMap + { + static Dictionary Types = new Dictionary(); + + public static void Use() where TImplementation : TService + { + Types[typeof(TService)] = typeof(TImplementation); + } + + + public static Type Get() => Get(typeof(T)); + + + public static Type Get(Type type) + { + if (!Types.ContainsKey(type)) + { + return null; + } + + return Types[type]; + } + } + + + public class EntityDefinition where TImplementation : TService + { + public Type ServiceType => typeof(TService); + + public Type ImplementationType => typeof(TImplementation); + + public IList Validators { get; set; } = new List(); + + public AbstractRenderer Renderer { get; set; } + } + + public class EntityDefinition + { + public Type ServiceType { get; protected set; } + + public Type ImplementationType { get; protected set; } + + public IList Validators { get; set; } = new List(); + + public AbstractGenericRenderer Renderer { get; set; } + + public static EntityDefinition Convert(EntityDefinition definition) where TImplementation : TService + { + return new EntityDefinition() + { + ServiceType = definition.ServiceType, + ImplementationType = definition.ImplementationType, + Validators = definition.Validators, + Renderer = definition.Renderer.ToGenericRenderer() + }; + } + } +} diff --git a/zero.Core/Extensions/DocumentStoreExtensions.cs b/zero.Core/Extensions/DocumentStoreExtensions.cs index dd7a8466..9b746d85 100644 --- a/zero.Core/Extensions/DocumentStoreExtensions.cs +++ b/zero.Core/Extensions/DocumentStoreExtensions.cs @@ -33,7 +33,7 @@ namespace zero.Core.Extensions } // use name from attribute if available - CollectionAttribute collection = type.GetCustomAttribute(); + CollectionAttribute collection = type.GetCustomAttribute(true); if (collection != null) { return collection.Name; diff --git a/zero.Core/ServiceCollectionExtensions.cs b/zero.Core/ServiceCollectionExtensions.cs index d15b67aa..aca1f764 100644 --- a/zero.Core/ServiceCollectionExtensions.cs +++ b/zero.Core/ServiceCollectionExtensions.cs @@ -1,4 +1,9 @@ -using Microsoft.Extensions.DependencyInjection; +using FluentValidation; +using Microsoft.Extensions.DependencyInjection; +using Scrutor; +using System; +using System.Linq; +using System.Reflection; using zero.Core.Api; namespace zero.Core.Entities @@ -7,8 +12,63 @@ namespace zero.Core.Entities { public static void AddZeroCoreServices(this IServiceCollection services) { - services.AddTransient(); + //services.Scan(scan => scan.FromAssemblyOf() + // .AddClasses(classes => classes.AssignableTo(typeof(IValidator<>))) + // .AsImplementedInterfaces() + // //.As(typeof(IValidator<>)) + // .WithScopedLifetime() + //); + + //Assembly[] assemblies = new Assembly[1] { typeof(IApplication).Assembly }; + + services.AddAllByInterfaceTransient(typeof(IValidator), typeof(IValidator<>), AppDomain.CurrentDomain.GetAssemblies()); + + services.AddTransient(); services.AddTransient(typeof(IApplicationsApi<>), typeof(ApplicationsApi<>)); } + + + public static void AddAllByInterfaceTransient(this IServiceCollection services, Assembly[] assemblies) + { + services.AddAllByInterfaceTransient(typeof(TService), typeof(TImplementation), assemblies); + //Type searchFor = typeof(TService); + //string searchForName = nameof(TService); // + (isGeneric ? "`1" : String.Empty); + + //foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) + //{ + // foreach (Type type in assembly.GetExportedTypes()) + // { + // if (searchFor.IsAssignableFrom(type) && type.IsClass) + // { + // Type service = type.GetInterfaces().FirstOrDefault(x => x.IsInterface && x.Name == searchForName); + + // if (service != null) + // { + // services.AddTransient(service, type); + // } + // } + // } + //} + } + + + public static void AddAllByInterfaceTransient(this IServiceCollection services, Type serviceType, Type implementingType, Assembly[] assemblies) + { + foreach (Assembly assembly in assemblies) + { + foreach (Type type in assembly.GetExportedTypes()) + { + if (serviceType.IsAssignableFrom(type) && type.IsClass && !type.IsAbstract && !type.FullName.StartsWith("Fluent")) + { + Type service = type.GetInterfaces().FirstOrDefault(x => x.IsInterface && x.Name == implementingType.Name); + + if (service != null) + { + services.AddTransient(service, type); + } + } + } + } + } } } diff --git a/zero.Core/zero.Core.csproj b/zero.Core/zero.Core.csproj index f5022c80..65199ec5 100644 --- a/zero.Core/zero.Core.csproj +++ b/zero.Core/zero.Core.csproj @@ -15,6 +15,7 @@ + diff --git a/zero.Debug/Controllers/TestController.cs b/zero.Debug/Controllers/TestController.cs index a8299951..3bfbec12 100644 --- a/zero.Debug/Controllers/TestController.cs +++ b/zero.Debug/Controllers/TestController.cs @@ -1,5 +1,7 @@ -using Microsoft.AspNetCore.Mvc; +using FluentValidation; +using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.Extensions.DependencyInjection; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -24,6 +26,37 @@ namespace zero.Debug.Controllers } + [HttpGet] + public IActionResult Services() + { + List items = new List(); + + foreach (var service in Startup.Services) + { + if (!service.ServiceType.FullName.StartsWith("zero.") && (service.ImplementationType == null || !service.ImplementationType.FullName.StartsWith("zero."))) + { + continue; + } + + items.Add(new + { + name = service.ServiceType.FullName, + lifetime = service.Lifetime, + instance = service.ImplementationType?.FullName + }); + } + + return Json(items); + } + + + [HttpGet] + public IActionResult Things([FromServices] IValidator appValidator) + { + return Ok(); + } + + [HttpGet] public async Task Index() { diff --git a/zero.Debug/MyApplication.cs b/zero.Debug/MyApplication.cs index 8a191f23..b6c8c9f0 100644 --- a/zero.Debug/MyApplication.cs +++ b/zero.Debug/MyApplication.cs @@ -1,9 +1,23 @@ -using zero.Core.Entities; +using FluentValidation; +using zero.Core.Attributes; +using zero.Core.Entities; +using zero.Core.Validation; namespace zero.Debug { + [Collection("Applications")] public class MyApplication : Application { public string Description { get; set; } } + + public class MyApplicationValidator : AbstractValidator + { + public MyApplicationValidator() + { + Include(new ApplicationValidator()); + + RuleFor(x => x.Description).NotEmpty(); + } + } } diff --git a/zero.Debug/Startup.cs b/zero.Debug/Startup.cs index 3ec7942d..b224b26e 100644 --- a/zero.Debug/Startup.cs +++ b/zero.Debug/Startup.cs @@ -21,11 +21,14 @@ namespace zero.Debug } + public static IServiceCollection Services; // This method gets called by the runtime. Use this method to add services to the container. // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { + Services = services; + services.AddAuthentication(options => { options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; diff --git a/zero.Debug/TestData/TestPlugin.cs b/zero.Debug/TestData/TestPlugin.cs index d10138cf..4bc6c912 100644 --- a/zero.Debug/TestData/TestPlugin.cs +++ b/zero.Debug/TestData/TestPlugin.cs @@ -1,7 +1,10 @@ using Microsoft.Extensions.DependencyInjection; using System.Collections.Generic; +using zero.Core; +using zero.Core.Entities; using zero.Core.Options; using zero.Core.Plugins; +using zero.Debug; using zero.Debug.TestData; using zero.TestData.Lists; @@ -37,6 +40,8 @@ namespace zero.TestData public void ConfigureServices(IServiceCollection services) { + EntityMap.Use(); + services.AddTransient(); } } diff --git a/zero.Web.UI/App/pages/settings/application.vue b/zero.Web.UI/App/pages/settings/application.vue index 6d75321c..12a30d23 100644 --- a/zero.Web.UI/App/pages/settings/application.vue +++ b/zero.Web.UI/App/pages/settings/application.vue @@ -106,7 +106,7 @@ form.load(!this.id ? ApplicationsApi.getEmpty() : ApplicationsApi.getById(this.id)).then(response => { this.disabled = !response.canEdit; - this.model = response; + this.model = response.entity; }); ApplicationsApi.getAllFeatures().then(items => diff --git a/zero.Web/ApiControllerFeatureProvider.cs b/zero.Web/ApiControllerFeatureProvider.cs index a209eb18..a5913cf2 100644 --- a/zero.Web/ApiControllerFeatureProvider.cs +++ b/zero.Web/ApiControllerFeatureProvider.cs @@ -2,33 +2,43 @@ using Microsoft.AspNetCore.Mvc.Controllers; using System; using System.Collections.Generic; +using System.Linq; using System.Reflection; -using System.Text; -using zero.Core.Entities; -using zero.Web.Controllers; +using zero.Core; +using zero.Web.Filters; namespace zero.Web { public class ApiControllerFeatureProvider : IApplicationFeatureProvider { + public ApiControllerFeatureProvider() + { + + } + + public void PopulateFeature(IEnumerable parts, ControllerFeature feature) { - var ctrls = feature.Controllers; + Assembly currentAssembly = typeof(ApiControllerFeatureProvider).Assembly; - var type = typeof(ApplicationsController<>).MakeGenericType(typeof(Application)).GetTypeInfo(); + IEnumerable candidates = currentAssembly.GetExportedTypes().Where(x => x.GetCustomAttributes().Any() && x.ContainsGenericParameters); - feature.Controllers.Add(type); - //var controllerType = typeof(GenericController<>).MakeGenericType(entityType.AsType()).GetTypeInfo(); + foreach (Type candidate in candidates) + { + Type genericType = candidate.GetGenericTypeDefinition(); + Type[] arguments = genericType.GetGenericArguments(); + Type desiredInterface = arguments.FirstOrDefault()?.GetInterfaces().FirstOrDefault(); - //feature.Controllers.Add() - //var typeName = entityType.Name + "Controller"; + if (desiredInterface == null) + { + continue; + } - //// Check to see if there is a "real" controller for this class - //if (!feature.Controllers.Any(t => t.Name == typeName)) - //{ - // // Create a generic controller for this type - // feature.Controllers.Add(controllerType); - //} + Type implementation = EntityMap.Get(desiredInterface); + TypeInfo type = candidate.MakeGenericType(implementation).GetTypeInfo(); + + feature.Controllers.Add(type); + } } } } diff --git a/zero.Web/ApplicationRenderer.cs b/zero.Web/ApplicationRenderer.cs new file mode 100644 index 00000000..8d75ce26 --- /dev/null +++ b/zero.Web/ApplicationRenderer.cs @@ -0,0 +1,19 @@ +using FluentValidation; +using zero.Core.Entities; +using zero.Core.Renderer; + +namespace zero.Web +{ + public class ApplicationRenderer : AbstractRenderer + { + public ApplicationRenderer() + { + Alias = "debug.contentpage"; + + LabelTemplate = "@_test.fields.{0}"; + DescriptionTemplate = "@_test.fields.{0}_text"; + + Field(x => x.Name, required: true).Text(); + } + } +} diff --git a/zero.Web/Controllers/Applications2Controller.cs b/zero.Web/Controllers/Applications2Controller.cs deleted file mode 100644 index 6f747713..00000000 --- a/zero.Web/Controllers/Applications2Controller.cs +++ /dev/null @@ -1,73 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using System.Threading.Tasks; -using zero.Core.Api; -using zero.Core.Entities; -using zero.Core.Identity; -using zero.Web.Filters; -using zero.Web.Models; - -namespace zero.Web.Controllers -{ - [ZeroAuthorize(Permissions.Settings.Applications, PermissionsValue.Read)] - [BackofficeGenericController] - public class ApplicationsController : BackofficeController where T : IApplication - { - IApplicationsApi Api; - - public ApplicationsController(IApplicationsApi api) - { - Api = api; - } - - - /// - /// Get translation by id - /// - public IActionResult GetEmpty([FromServices] T app) => JsonEdit(app); - - - /// - /// Get translation by id - /// - public async Task GetById([FromQuery] string id) => JsonEdit(await Api.GetById(id)); - - - /// - /// Get all translations - /// - public async Task GetAll([FromQuery] ListQuery query = null) - { - if (query == null) - { - return Json(await Api.GetAll()); - } - - return Json(await Api.GetByQuery(query)); - } - - - /// - /// Get all available features to select - /// - public IActionResult GetAllFeatures() => Json(Options.Features.GetAllItems()); - - - /// - /// Save translation - /// - [ZeroAuthorize(Permissions.Settings.Applications, PermissionsValue.Write)] - public async Task Save([FromBody] EditModel model) - { - return Ok(); - //Application entity = await Mapper.Map(model, await Api.GetById(null)); - //return await As(await Api.Save(entity)); - } - - - /// - /// Deletes a translation - /// - [ZeroAuthorize(Permissions.Settings.Applications, PermissionsValue.Write)] - public async Task Delete([FromQuery] string id) => JsonEdit(await Api.Delete(id)); - } -} diff --git a/zero.Web/Controllers/ApplicationsController.cs b/zero.Web/Controllers/ApplicationsController.cs index 6c34ce08..d0802e60 100644 --- a/zero.Web/Controllers/ApplicationsController.cs +++ b/zero.Web/Controllers/ApplicationsController.cs @@ -1,93 +1,65 @@ -//using Microsoft.AspNetCore.Mvc; -//using System.Threading.Tasks; -//using zero.Core.Api; -//using zero.Core.Entities; -//using zero.Core.Identity; -//using zero.Core.Options; -//using zero.Web.Models; +using Microsoft.AspNetCore.Mvc; +using System.Threading.Tasks; +using zero.Core.Api; +using zero.Core.Entities; +using zero.Core.Identity; -//namespace zero.Web.Controllers -//{ -// [ZeroAuthorize(Permissions.Settings.Applications, PermissionsValue.Read)] -// public class ApplicationsController : BackofficeController -// { -// IApplicationsApi Api; +namespace zero.Web.Controllers +{ + [ZeroAuthorize(Permissions.Settings.Applications, PermissionsValue.Read)] + public class ApplicationsController : BackofficeController where T : IApplication + { + IApplicationsApi Api; -// public ApplicationsController(IApplicationsApi api) -// { -// Api = api; -// } + public ApplicationsController(IApplicationsApi api) + { + Api = api; + } -// /// -// /// Get translation by id -// /// -// public IActionResult GetEmpty([FromServices] IApplication app) -// { -// return JsonEdit(app); -// } + /// + /// Get translation by id + /// + public IActionResult GetEmpty([FromServices] T app) => JsonEdit(app); -// /// -// /// Get translation by id -// /// -// public async Task GetById([FromQuery] string id) -// { -// return JsonEdit(await Api.GetById(id)); -// } + /// + /// Get translation by id + /// + public async Task GetById([FromQuery] string id) => JsonEdit(await Api.GetById(id)); -// /// -// /// Get all translations -// /// -// public async Task GetAll([FromQuery] ListQuery query = null) -// { -// if (query == null) -// { -// return Json(await Api.GetAll()); -// } + /// + /// Get all translations + /// + public async Task GetAll([FromQuery] ListQuery query = null) + { + if (query == null) + { + return Json(await Api.GetAll()); + } -// return Json(await Api.GetByQuery(query)); -// } + return Json(await Api.GetByQuery(query)); + } -// /// -// /// Get all available features to select -// /// -// public IActionResult GetAllFeatures() -// { -// return Json(Options.Features.GetAllItems()); -// } + /// + /// Get all available features to select + /// + public IActionResult GetAllFeatures() => Json(Options.Features.GetAllItems()); -// /// -// /// Save translation -// /// -// [ZeroAuthorize(Permissions.Settings.Applications, PermissionsValue.Write)] -// public async Task Save([FromBody] EditModel model) -// { -// return Ok(); -// //Application entity = await Mapper.Map(model, await Api.GetById(null)); -// //return await As(await Api.Save(entity)); -// } + /// + /// Save translation + /// + [ZeroAuthorize(Permissions.Settings.Applications, PermissionsValue.Write)] + public async Task Save([FromBody] T model) => Json(await Api.Save(model)); -// /// -// /// Deletes a translation -// /// -// [ZeroAuthorize(Permissions.Settings.Applications, PermissionsValue.Write)] -// public async Task Delete([FromQuery] string id) -// { -// return JsonEdit(await Api.Delete(id)); -// } - - -// /// -// /// Save translation -// /// -// //public async Task Switch([FromQuery] string id) -// //{ -// // return await As(await Api.Save(entity)); -// //} -// } -//} + /// + /// Deletes a translation + /// + [ZeroAuthorize(Permissions.Settings.Applications, PermissionsValue.Write)] + public async Task Delete([FromQuery] string id) => Json(await Api.Delete(id)); + } +} diff --git a/zero.Web/Controllers/BackofficeController.cs b/zero.Web/Controllers/BackofficeController.cs index a02c79d9..a650b511 100644 --- a/zero.Web/Controllers/BackofficeController.cs +++ b/zero.Web/Controllers/BackofficeController.cs @@ -13,6 +13,7 @@ using zero.Web.Models; namespace zero.Web.Controllers { [ZeroAuthorize, CanEdit, AddToken] + [BackofficeGenericController] public abstract class BackofficeController : Controller { IMapper _mapper; @@ -42,7 +43,8 @@ namespace zero.Web.Controllers { return Json(new EditModel() { - Entity = data + Entity = data, + CanEdit = true }); } diff --git a/zero.Web/ZeroBuilder.cs b/zero.Web/ZeroBuilder.cs index e1245e49..16d641de 100644 --- a/zero.Web/ZeroBuilder.cs +++ b/zero.Web/ZeroBuilder.cs @@ -137,7 +137,7 @@ namespace zero.Web mvc.ConfigureApplicationPartManager(setup => { - setup.FeatureProviders.Add(new Web.ApiControllerFeatureProvider()); + setup.FeatureProviders.Add(new ApiControllerFeatureProvider()); }); return mvc; @@ -239,6 +239,19 @@ namespace zero.Web } + private static EntityDefinition[] Entities = new EntityDefinition[] { }; + + //public ZeroPlugin Use(Action> configure) where TImplementation : TService + //{ + // //configure() + //} + + //public ZeroPlugin Use() where TImplementation : TService + //{ + // //configure() + //} + + /// /// Adds a zero plugin /// diff --git a/zero.sln b/zero.sln index b15ca3fe..94ea5e95 100644 --- a/zero.sln +++ b/zero.sln @@ -36,11 +36,6 @@ Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "zero.Web.UI", "zero.Web.UI\ SlnRelativePath = "zero.Web.UI\" EndProjectSection EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{08BF691F-450C-4C85-9953-D250E4DF7F41}" - ProjectSection(SolutionItems) = preProject - AssemblyInfo.cs = AssemblyInfo.cs - EndProjectSection -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU