From af723eed68d63a53b937b50e2ee6213e6ef2ca1e Mon Sep 17 00:00:00 2001 From: wanggang <76527413@qq.com> Date: Wed, 8 Apr 2020 14:55:38 +0800 Subject: [PATCH] 1.0.0-beta.408 Former-commit-id: 9351510c2f675ac4be34739de73f99ef27d219aa --- .../Application/Entites/Settings/Setting.cs | 3 + .../Application/Models/EditSettingModel.cs | 2 +- .../Areas/Admin/Views/Setting/Index.cshtml | 2 +- projects/Infrastructure/Infrastructure.csproj | 1 + .../Infrastructure/Views/Shared/Index.cshtml | 2 +- projects/Infrastructure/Web/BaseStartup.cs | 30 +- .../AdditionalMetadataAttribute.cs | 0 .../GeneratedControllerAttribute.cs | 15 + .../DynamicController/GenericController.cs | 322 ++++++++++++++++++ .../GenericControllerRouteConvention.cs | 25 ++ .../GenericTypeControllerFeatureProvider.cs | 24 ++ .../Application/Domain/Entities/User.cs | 31 +- 12 files changed, 440 insertions(+), 17 deletions(-) rename projects/Infrastructure/Web/Mvc/{ => DynamicController}/AdditionalMetadataAttribute.cs (100%) create mode 100644 projects/Infrastructure/Web/Mvc/DynamicController/GeneratedControllerAttribute.cs create mode 100644 projects/Infrastructure/Web/Mvc/DynamicController/GenericController.cs create mode 100644 projects/Infrastructure/Web/Mvc/DynamicController/GenericControllerRouteConvention.cs create mode 100644 projects/Infrastructure/Web/Mvc/DynamicController/GenericTypeControllerFeatureProvider.cs diff --git a/projects/Infrastructure/Application/Entites/Settings/Setting.cs b/projects/Infrastructure/Application/Entites/Settings/Setting.cs index d99acb30..18854df9 100644 --- a/projects/Infrastructure/Application/Entites/Settings/Setting.cs +++ b/projects/Infrastructure/Application/Entites/Settings/Setting.cs @@ -3,11 +3,14 @@ using System.ComponentModel.DataAnnotations; namespace Infrastructure.Application.Entites.Settings { + [Display(Name = "配置")] public class Setting : BaseEntity { [Required] public string Name { get; set; } + public string Value { get; set; } + public SettingType Type { get; set; } } } \ No newline at end of file diff --git a/projects/Infrastructure/Application/Models/EditSettingModel.cs b/projects/Infrastructure/Application/Models/EditSettingModel.cs index fefd44db..6d12196b 100644 --- a/projects/Infrastructure/Application/Models/EditSettingModel.cs +++ b/projects/Infrastructure/Application/Models/EditSettingModel.cs @@ -3,7 +3,7 @@ using System.ComponentModel.DataAnnotations; namespace Infrastructure.Application.Models { - [Display(Name = "属性")] + [Display(Name = "配置")] public class EditSettingModel : EditModel { [Display(Name = "属性名称")] diff --git a/projects/Infrastructure/Areas/Admin/Views/Setting/Index.cshtml b/projects/Infrastructure/Areas/Admin/Views/Setting/Index.cshtml index cdb681e7..a7899aab 100644 --- a/projects/Infrastructure/Areas/Admin/Views/Setting/Index.cshtml +++ b/projects/Infrastructure/Areas/Admin/Views/Setting/Index.cshtml @@ -2,7 +2,7 @@ @using System.Security.Claims @model PagedList @{ - HtmlTitle = "设置"; + HtmlTitle = "配置"; var start = 0; var entityName = "Setting"; var hasPermissions = (User.Identity as ClaimsIdentity).Claims.Any(o => o.Value.EndsWith($"-{entityName}")); diff --git a/projects/Infrastructure/Infrastructure.csproj b/projects/Infrastructure/Infrastructure.csproj index e3b05c4d..05b0c20b 100644 --- a/projects/Infrastructure/Infrastructure.csproj +++ b/projects/Infrastructure/Infrastructure.csproj @@ -5,6 +5,7 @@ true true true + 1.0.0-beta.408 diff --git a/projects/Infrastructure/Views/Shared/Index.cshtml b/projects/Infrastructure/Views/Shared/Index.cshtml index 83d46a5b..3912c7ba 100644 --- a/projects/Infrastructure/Views/Shared/Index.cshtml +++ b/projects/Infrastructure/Views/Shared/Index.cshtml @@ -46,7 +46,7 @@ }
-
+
diff --git a/projects/Infrastructure/Web/BaseStartup.cs b/projects/Infrastructure/Web/BaseStartup.cs index a924ab1e..a2d60d0c 100644 --- a/projects/Infrastructure/Web/BaseStartup.cs +++ b/projects/Infrastructure/Web/BaseStartup.cs @@ -8,6 +8,7 @@ using Infrastructure.Office; using Infrastructure.Security; using Infrastructure.UI; using Infrastructure.Web.Authentication.Cookies; +using Infrastructure.Web.Mvc; using Infrastructure.Web.Mvc.ModelBinding.Metadata; using Infrastructure.Web.SignalR; using Microsoft.AspNetCore.Authentication.Cookies; @@ -105,19 +106,24 @@ namespace Infrastructure.Web //https://github.com/aspnet/Entropy/blob/master/samples/Localization.StarterWeb/Startup.cs services.AddLocalization(options => options.ResourcesPath = null); //https://yetawf.com/BlogEntry/Title/AdditionalMetadataAttribute%20Anyone/?BlogEntry=1005 - services.AddMvc(o => o.ModelMetadataDetailsProviders.Add(new AdditionalMetadataProvider())) - .SetCompatibilityVersion(CompatibilityVersion.Latest) - .AddNewtonsoftJson() - .AddControllersAsServices() - .AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix) - .AddDataAnnotationsLocalization(options => + services.AddMvc(o => + { + o.ModelMetadataDetailsProviders.Add(new AdditionalMetadataProvider()); + o.Conventions.Add(new GenericControllerRouteConvention()); + }).ConfigureApplicationPartManager(m => m.FeatureProviders.Add(new GenericTypeControllerFeatureProvider())) + .SetCompatibilityVersion(CompatibilityVersion.Latest) + .AddNewtonsoftJson() + .AddControllersAsServices() + .AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix) + .AddDataAnnotationsLocalization(options => + { + options.DataAnnotationLocalizerProvider = (type, factory) => { - options.DataAnnotationLocalizerProvider = (type, factory) => - { - var localizer = factory.Create("Resources.Resource", Assembly.GetEntryAssembly().FullName); - return localizer; - }; - }); + var localizer = factory.Create("Resources.Resource", Assembly.GetEntryAssembly().FullName); + return localizer; + }; + }); + services.AddApiVersioning(o => { o.ReportApiVersions = true; diff --git a/projects/Infrastructure/Web/Mvc/AdditionalMetadataAttribute.cs b/projects/Infrastructure/Web/Mvc/DynamicController/AdditionalMetadataAttribute.cs similarity index 100% rename from projects/Infrastructure/Web/Mvc/AdditionalMetadataAttribute.cs rename to projects/Infrastructure/Web/Mvc/DynamicController/AdditionalMetadataAttribute.cs diff --git a/projects/Infrastructure/Web/Mvc/DynamicController/GeneratedControllerAttribute.cs b/projects/Infrastructure/Web/Mvc/DynamicController/GeneratedControllerAttribute.cs new file mode 100644 index 00000000..344db3e9 --- /dev/null +++ b/projects/Infrastructure/Web/Mvc/DynamicController/GeneratedControllerAttribute.cs @@ -0,0 +1,15 @@ +using System; + +namespace Infrastructure.Web.Mvc +{ + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class GeneratedControllerAttribute : Attribute + { + public GeneratedControllerAttribute(string route = null) + { + Route = route; + } + + public string Route { get; set; } + } +} \ No newline at end of file diff --git a/projects/Infrastructure/Web/Mvc/DynamicController/GenericController.cs b/projects/Infrastructure/Web/Mvc/DynamicController/GenericController.cs new file mode 100644 index 00000000..bf1dd61d --- /dev/null +++ b/projects/Infrastructure/Web/Mvc/DynamicController/GenericController.cs @@ -0,0 +1,322 @@ +using Infrastructure.Application; +using Infrastructure.Data; +using Infrastructure.Domain; +using Infrastructure.Extensions; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Infrastructure.Web.Mvc +{ + //[ApiExplorerSettings(IgnoreApi = false)] + //[ApiController] + //[Route("Entity/[controller]/[action]")] + [Area("Entity")] + public class GenericController : BaseController where T : BaseEntity + { + protected readonly IRepository _repo; + + public GenericController(IRepository repo) + { + this._repo = repo; + } + + //[Route("")] + //[Route("Index")] + [HttpGet] + public virtual IActionResult Index(PagedListModel model) + { + if (model == null) + { + throw new ArgumentNullException(nameof(model)); + } + var query = this._repo.ReadOnlyTable(); + if (model is ISoftDeleteEntity && !model.IsDeleted) + { + query = query.Where(o => o.IsDeleted == null); + } + query = this.Include(query); + query = this.Query(model, query); + model.TotalCount = query.Count(); + + model.List.AddRange(query.Skip(model.PageSize * (model.PageIndex - 1)) + .Take(model.PageSize) + .ToList() + .Select(o => + { + var m = o.To(); + this.ToDisplayModel(o, m); + return m; + }) + .ToList()); + ViewData["EntityTypeExt"] = typeof(T); + ViewData["ModelTypeExt"] = typeof(T); + return this.Result(model); + } + + [HttpGet] + public virtual IActionResult Details(Guid id) + { + var query = this._repo.ReadOnlyTable(); + query = this.Include(query); + var entity = query.FirstOrDefault(o => o.Id == id); + var model = entity.To(); + this.ToDisplayModel(entity, model); + return Result(model); + } + + [HttpGet] + public virtual IActionResult Add() + { + var model = Activator.CreateInstance(); + this.ToEditModel(null, model); + return View(model); + } + + [HttpPost] + public virtual IActionResult Add(T model) + { + if (ModelState.IsValid) + { + try + { + var entity = Activator.CreateInstance(); + entity.From(model); + this.ToEntity(model, entity); + this._repo.Add(entity); + if (this._repo.SaveChanges() > 0) + { + this.OnInserted(entity); + } + return RedirectTo(); + } + catch (DbUpdateException ex) + { + ex.PrintStack(); + ModelState.AddModelError("", ex.Message); + } + } + this.ToEditModel(null, model); + return View(model); + } + + [HttpGet] + public virtual IActionResult Edit(Guid id) + { + var query = this._repo.ReadOnlyTable(); + query = this.Include(query); + var entity = query.FirstOrDefault(o => o.Id == id); + var model = entity.To(); + this.ToEditModel(entity, model); + return View(model); + } + + [HttpPost] + public virtual IActionResult Edit(T model) + { + var id = (Guid)model.GetType().GetProperty("Id").GetValue(model); + var query = this._repo.Table(); + query = this.Include(query); + var entity = query.FirstOrDefault(o => o.Id == id); + if (ModelState.IsValid) + { + try + { + this.OnEdit(entity, model); + entity.From(model); + this.ToEntity(model, entity); + if (this._repo.SaveChanges() > 0) + { + this.OnUpdated(entity); + } + return RedirectTo(); + } + catch (DbUpdateException ex) + { + ex.PrintStack(); + ModelState.AddModelError("", ex.Message); + } + } + this.ToEditModel(entity, model); + return View(model); + } + + [HttpPost] + public virtual IActionResult Remove(List list) + { + if (list == null) + { + throw new ArgumentNullException(nameof(list)); + } + try + { + foreach (var id in list) + { + var query = this._repo.Table(); + var entity = query.FirstOrDefault(o => o.Id == id); + entity.IsDeleted = Guid.NewGuid().ToString(); + this._repo.SaveChanges(); + } + return RedirectTo(); + } + catch (DbUpdateException ex) + { + ex.PrintStack(); + return RedirectTo(rawMesage: ex.Message); + } + } + + [HttpPost] + public IActionResult Restore(List list) + { + if (list == null) + { + throw new ArgumentNullException(nameof(list)); + } + try + { + foreach (var id in list) + { + var query = this._repo.Table(); + var entity = query.FirstOrDefault(o => o.Id == id); + entity.IsDeleted = null; + this._repo.SaveChanges(); + } + return RedirectTo(); + } + catch (DbUpdateException ex) + { + ex.PrintStack(); + return RedirectTo(rawMesage: ex.Message); + } + } + + [HttpPost] + public virtual IActionResult Delete(List list) + { + if (list == null) + { + throw new ArgumentNullException(nameof(list)); + } + try + { + foreach (var id in list) + { + var query = this._repo.Table(); + var entity = query.FirstOrDefault(o => o.Id == id); + this._repo.Delete(entity); + if (this._repo.SaveChanges() > 0) + { + this.OnDeleted(entity); + } + } + return RedirectTo(); + } + catch (DbUpdateException ex) + { + ex.PrintStack(); + return RedirectTo(rawMesage: ex.Message); + } + } + + [ApiExplorerSettings(IgnoreApi = true)] + public virtual IQueryable Include(IQueryable query) + { + return query; + } + + [ApiExplorerSettings(IgnoreApi = true)] + public virtual IQueryable Query(PagedListModel model, IQueryable query) + { + return query; + } + + [ApiExplorerSettings(IgnoreApi = true)] + public virtual void ToEditModel(T entity, T model) + { + } + + [ApiExplorerSettings(IgnoreApi = true)] + public virtual void ToDisplayModel(T entity, T model) + { + } + + [ApiExplorerSettings(IgnoreApi = true)] + public virtual void ToEntity(T model, T entity) + { + } + + [ApiExplorerSettings(IgnoreApi = true)] + public virtual void OnEdit(T entity, T model) + { + } + + [ApiExplorerSettings(IgnoreApi = true)] + public virtual void OnInserted(T entity) + { + } + + [ApiExplorerSettings(IgnoreApi = true)] + public virtual void OnUpdated(T entity) + { + } + + [ApiExplorerSettings(IgnoreApi = true)] + public virtual void OnDeleted(T entity) + { + } + + [ApiExplorerSettings(IgnoreApi = true)] + public override void OnActionExecuting(ActionExecutingContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + var name = (context.ActionDescriptor as ControllerActionDescriptor).ActionName; + var permission = string.Empty; + if (name == "Index") + { + permission = $"Read-{typeof(T).Name}"; + } + else if (name == "Details") + { + permission = $"Read-{typeof(T).Name}"; + } + else if (name == "Add") + { + permission = $"Add-{typeof(T).Name}"; + } + else if (name == "Edit" || name == "Remove" || name == "Restore") + { + permission = $"Edit-{typeof(T).Name}"; + } + else if (name == "Delete") + { + permission = $"Delete-{typeof(T).Name}"; + } + if (!string.IsNullOrEmpty(permission)) + { + var returnUrl = context.HttpContext.Request.GetUrl(); + if (!context.HttpContext.User.Identity.IsAuthenticated) + { + context.Result = new RedirectToActionResult("Login", "Account", new { area = "", returnUrl }); ; + } + else if (!context.HttpContext.User.IsInRole(permission)) + { + context.Result = new RedirectToActionResult("AccessDenied", "Account", new { area = "", returnUrl }); + } + } + } + + [ApiExplorerSettings(IgnoreApi = true)] + private IActionResult Result(object model) + { + return this.Request.Headers["accept"].ToString().Contains("json") ? Json(model) as IActionResult : View(model); + } + } +} \ No newline at end of file diff --git a/projects/Infrastructure/Web/Mvc/DynamicController/GenericControllerRouteConvention.cs b/projects/Infrastructure/Web/Mvc/DynamicController/GenericControllerRouteConvention.cs new file mode 100644 index 00000000..d7aa668d --- /dev/null +++ b/projects/Infrastructure/Web/Mvc/DynamicController/GenericControllerRouteConvention.cs @@ -0,0 +1,25 @@ +using Microsoft.AspNetCore.Mvc.ApplicationModels; +using System.Reflection; + +namespace Infrastructure.Web.Mvc +{ + public class GenericControllerRouteConvention : IControllerModelConvention + { + public void Apply(ControllerModel controller) + { + if (controller.ControllerType.IsGenericType) + { + var genericType = controller.ControllerType.GenericTypeArguments[0]; + var customNameAttribute = genericType.GetCustomAttribute(); + if (customNameAttribute != null) + { + controller.ControllerName = genericType.Name; + //controller.Selectors.Add(new SelectorModel + //{ + // AttributeRouteModel = new AttributeRouteModel(new RouteAttribute(customNameAttribute.Route ?? $"Entity/[controller]/[action]")), + //}); + } + } + } + } +} \ No newline at end of file diff --git a/projects/Infrastructure/Web/Mvc/DynamicController/GenericTypeControllerFeatureProvider.cs b/projects/Infrastructure/Web/Mvc/DynamicController/GenericTypeControllerFeatureProvider.cs new file mode 100644 index 00000000..302b3381 --- /dev/null +++ b/projects/Infrastructure/Web/Mvc/DynamicController/GenericTypeControllerFeatureProvider.cs @@ -0,0 +1,24 @@ +using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.AspNetCore.Mvc.Controllers; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Infrastructure.Web.Mvc +{ + public class GenericTypeControllerFeatureProvider : IApplicationFeatureProvider + { + public void PopulateFeature(IEnumerable parts, ControllerFeature feature) + { + var currentAssembly = Assembly.GetEntryAssembly(); + var candidates = currentAssembly.GetExportedTypes().Where(x => x.GetCustomAttributes(typeof(GeneratedControllerAttribute), false).Any()).ToList(); + + foreach (var candidate in candidates) + { + feature.Controllers.Add( + typeof(GenericController<>).MakeGenericType(candidate).GetTypeInfo() + ); + } + } + } +} \ No newline at end of file diff --git a/projects/UserCenter/Application/Domain/Entities/User.cs b/projects/UserCenter/Application/Domain/Entities/User.cs index 70b49bcb..9607ab64 100644 --- a/projects/UserCenter/Application/Domain/Entities/User.cs +++ b/projects/UserCenter/Application/Domain/Entities/User.cs @@ -1,4 +1,5 @@ using Infrastructure.Domain; +using Infrastructure.Web.Mvc; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; @@ -6,8 +7,10 @@ using System.ComponentModel.DataAnnotations; namespace Application.Domain.Entities { [Display(Name = "用户")] + [GeneratedController] public class User : BaseEntity, ISoftDeleteEntity, IVersionEntity { + [Display(Name = "用户名")] public string UserName { get; set; } public string SecurityStamp { get; set; } @@ -15,43 +18,67 @@ namespace Application.Domain.Entities public string PasswordHash { get; set; } public bool PasswordConfirmed { get; set; } + + [Display(Name = "邮箱")] public string Email { get; set; } + [Display(Name = "邮箱已认证")] public bool EmailConfirmed { get; set; } - public bool PhoneNumberConfirmed { get; set; } - + [Display(Name = "手机号")] public string PhoneNumber { get; set; } + [Display(Name = "手机已认证")] + public bool PhoneNumberConfirmed { get; set; } + + [Display(Name = "支付密码")] public string PayPassword { get; set; } + [Display(Name = "支付密码已认证")] public bool PayPasswordConfirmed { get; set; } + [Display(Name = "真实姓名")] public string RealName { get; set; } + [Display(Name = "身份证号")] public string IdCardNumber { get; set; } + [Display(Name = "已实名认证")] public bool IdentityConfirmed { get; set; } + [Display(Name = "昵称")] public string NickName { get; set; } + [Display(Name = "头像")] public string Avatar { get; set; } + + [Display(Name = "人脸识别")] public string FaceImage { get; set; } + [Display(Name = "性别")] public Sex Sex { get; set; } + + [Display(Name = "生日")] public DateTime? Birthday { get; set; } + [Display(Name = "可用余额")] public decimal AvailBalance { get; set; } + [Display(Name = "冻结余额")] public decimal FrozenBlance { get; set; } + [Display(Name = "积分")] public long Point { get; set; } + [Display(Name = "启用登录锁定")] public bool LockoutEnabled { get; set; } + [Display(Name = "登录失败次数")] public int AccessFailedCount { get; set; } + [Display(Name = "锁定截止时间")] public DateTimeOffset? LockoutEnd { get; set; } + public string RowVersion { get; set; } public List UserRoles { get; set; } = new List(); public List UserDepartments { get; set; } = new List();