基于领域驱动设计(DDD)超轻量级快速开发架构

 

smartadmin.core.urf 这个项目是基于asp.net core 3.1(最新)基础上参照领域驱动设计(DDD)的理念,并参考目前最为了流行的abp架构开发的一套轻量级的快速开发web application 技术架构,专注业务核心需求,减少重复代码,开始构建和发布,让初级程序员也能开发出专业并且漂亮的Web应用程序

域驱动设计(DDD)是一种通过将实现与不断发展的模型相连接来满足复杂需求的软件开发方法。域驱动设计的前提如下:

  • 将项目的主要重点放在核心领域和领域逻辑上;
  • 将复杂的设计基于领域模型;
  • 启动技术专家和领域专家之间的创造性合作,以迭代方式完善解决特定领域问题的概念模型。

最终的核心思想还是SOLID,只是实现的方式有所不同,ABP可能目前对DDD设计理念最好的实现方式。但对于小项目我还是更喜欢 URF.Core https://github.com/urfnet/URF.Core 这个超轻量级的实现。

同时这个项目也就是我2年前的一个开源项目 ASP.NET MVC 5 SmartCode Scaffolding for Visual Studio.Net 的升级版,支持.net core.目前没有把所有功能都迁移到.net core,其中最重要的就是代码生成这块。再接下来的时间里主要就是完善代码生成的插件。当然也要看是否受欢迎,如果反应一般,我可能不会继续更新。

Demo 网站

 演示站点 
账号:demo 密码:123456

GitHub 源代码 https://github.com/neozhu/smartadmin.core.urf

喜欢请给个 Star 每一颗Star都是鼓励我继续更新的动力 谢谢
如果你用于自己公司及盈利性的项目,希望给与金钱上的赞助,并且保留原作者的版权

分层

smartadmin.core.urf遵行DDD设计模式来实现应用程序的四层模型

  • 表示层(Presentation Layer):用户操作展示界面,使用SmartAdmin - Responsive WebApp模板+Jquery EasyUI
  • 应用层(Application Layer):在表示层与域层之间,实现具体应用程序逻辑,业务用例,Project:StartAdmin.Service.csproj
  • 域层(Domain Layer):包括业务对象(Entity)和核心(域)业务规则,应用程序的核心,使用EntityFrmework Core Code-first + Repository实现
  • 基础结构层(Infrastructure Layer):提供通用技术功能,这些功能主要有第三方库来支持,比如日志:Nlog,服务发现:Swagger UI,事件总线(EventBus):dotnetcore/CAP,认证与授权:Microsoft.AspNetCore.Identity,后面会具体介绍

内容

 

域层(Domain Layer)

  • 实体(Entity,BaseEntity) 通常实体就是映射到关系数据库中的表,这里说名一下最佳做法和惯例:
  1. 在域层定义:本项目就是(SmartAdmin.Entity.csproj)
  2. 继承一个基类 Entity,添加必要审计类比如:创建时间,最后修改时间等
  3. 必须要有一个主键最好是GRUID(不推荐复合主键),但本项目使用递增的int类型
  4. 字段不要过多的冗余,可以通过定义关联关系
  5. 字段属性和方法尽量使用virtual关键字。有些ORM和动态代理工具需要

 

  • 存储库(Repositories) 封装基本数据操作方法(CRUD),本项目应用 URF.Core实现
  • 域服务
  • 技术指标
  • 应用层

    • 应用服务:用于实现应用程序的用例。它们用于将域逻辑公开给表示层,从表示层(可选)使用DTO(数据传输对象)作为参数调用应用程序服务。它使用域对象执行某些特定的业务逻辑,并(可选)将DTO返回到表示层。因此,表示层与域层完全隔离。对应本项目:(SmartAdmin.Service.csproj)
    • 数据传输对象(DTO):用于在应用程序层和表示层或其他类型的客户端之间传输数据,通常,使用DTO作为参数从表示层(可选)调用应用程序服务。它使用域对象执行某些特定的业务逻辑,并(可选)将DTO返回到表示层。因此,表示层与域层完全隔离.对应本项目:(SmartAdmin.Dto.csproj)
    • Unit of work:管理和控制应用程序中操作数据库连接和事务 ,本项目使用 URF.Core实现
  • 基础服务层

    • UI样式定义:根据用户喜好选择多种页面显示模式
    • 租户管理:使用EntityFrmework Core提供的Global Filter实现简单多租户应用
    • 账号管理: 对登录系统账号维护,注册,注销,锁定,解锁,重置密码,导入、导出等功能
    • 角色管理:使用Microsoft身份库管理角色,用户及其权限管理
    • 导航菜单:系统主导航栏配置
    • 角色授权:配置角色显示的菜单
    • 键值对配置:常用的数据字典维护,如何正确使用和想法后面会介绍
    • 导入&导出配置:使用Excel导入导出做一个可配置的功能
    • 系统日志:asp.net core 自带的日志+Nlog把所有日志保存到数据库方便查询和分析
    • 消息订阅:集中订阅CAP分布式事件总线的消息
    • WebApi: Swagger UI Api服务发现和在线调试工具
    • CAP: CAP看板查看发布和订阅的消息

快速上手开发

  • 开发环境
    • Visual Studio .Net 2019
    • .Net Core 3.1
    • Sql Server(LocalDb)
  • 附加数据库

    使用SQL Server Management Studio 附加.\src\SmartAdmin.Data\db\smartadmindb.mdf 数据库(如果是localdb,那么不需要修改数据库连接配置)

  • 打开解决方案

第一个简单的需求开始 
新增 Company 企业信息 完成CRUD 导入导出功能

  • 新建实体对象(Entity)

在SmartAdmin.Entity.csproj项目的Models目录下新增一个Company.cs类

 1 //记住:定义实体对象最佳做法,继承基类,使用virtual关键字,尽可能的定义每个属性,名称,类型,长度,校验规则,索引,默认值等
 2 namespace SmartAdmin.Data.Models
 3 {
 4     public partial class Company : URF.Core.EF.Trackable.Entity
 5     {
 6         [Display(Name = "企业名称", Description = "归属企业名称")]
 7         [MaxLength(50)]
 8         [Required]
 9         //[Index(IsUnique = true)]
10         public virtual string Name { get; set; }
11         [Display(Name = "组织代码", Description = "组织代码")]
12         [MaxLength(12)]
13         //[Index(IsUnique = true)]
14         [Required]
15         public virtual string Code { get; set; }
16         [Display(Name = "地址", Description = "地址")]
17         [MaxLength(128)]
18         [DefaultValue("-")]
19         public virtual string Address { get; set; }
20         [Display(Name = "联系人", Description = "联系人")]
21         [MaxLength(12)]
22         public virtual string Contect { get; set; }
23         [Display(Name = "联系电话", Description = "联系电话")]
24         [MaxLength(20)]
25         public virtual string PhoneNumber { get; set; }
26         [Display(Name = "注册日期", Description = "注册日期")]
27         [DefaultValue("now")]
28         public virtual  DateTime RegisterDate { get; set; }
29     }
30 }
31 //在 SmartAdmin.Data.csproj 项目 SmartDbContext.cs 添加
32 public virtual DbSet Companies { get; set; }
View Code
  • 添加服务对象 Service

在项目 SmartAdmin.Service.csproj 中添加ICompanyService.cs,CompanyService.cs 就是用来实现业务需求 用例的地方

  1 //ICompany.cs
  2 //根据实际业务用例来创建方法,默认的CRUD,增删改查不需要再定义
  3 namespace SmartAdmin.Service
  4 {
  5   // Example: extending IService and/or ITrackableRepository, scope: ICustomerService
  6   public interface ICompanyService : IService
  7   {
  8     // Example: adding synchronous Single method, scope: ICustomerService
  9     Company Single(Expressionbool>> predicate);
 10     Task ImportDataTableAsync(DataTable datatable);
 11     Task ExportExcelAsync(string filterRules = "", string sort = "Id", string order = "asc");
 12   }
 13 }
 14 // 具体实现接口的方法
 15 namespace SmartAdmin.Service
 16 {
 17   public class CompanyService : Service, ICompanyService
 18   {
 19     private readonly IDataTableImportMappingService mappingservice;
 20     private readonly ILogger logger;
 21     public CompanyService(
 22       IDataTableImportMappingService mappingservice,
 23       ILogger logger,
 24       ITrackableRepository repository) : base(repository)
 25     {
 26       this.mappingservice = mappingservice;
 27       this.logger = logger;
 28     }
 29 
 30     public async Task ExportExcelAsync(string filterRules = "", string sort = "Id", string order = "asc")
 31     {
 32       var filters = PredicateBuilder.FromFilter(filterRules);
 33       var expcolopts = await this.mappingservice.Queryable()
 34              .Where(x => x.EntitySetName == "Company")
 35              .Select(x => new ExpColumnOpts()
 36              {
 37                EntitySetName = x.EntitySetName,
 38                FieldName = x.FieldName,
 39                IgnoredColumn = x.IgnoredColumn,
 40                SourceFieldName = x.SourceFieldName
 41              }).ToArrayAsync();
 42 
 43       var works = (await this.Query(filters).OrderBy(n => n.OrderBy(sort, order)).SelectAsync()).ToList();
 44       var datarows = works.Select(n => new
 45       {
 46         Id = n.Id,
 47         Name = n.Name,
 48         Code = n.Code,
 49         Address = n.Address,
 50         Contect = n.Contect,
 51         PhoneNumber = n.PhoneNumber,
 52         RegisterDate = n.RegisterDate.ToString("yyyy-MM-dd HH:mm:ss")
 53       }).ToList();
 54       return await NPOIHelper.ExportExcelAsync("Company", datarows, expcolopts);
 55     }
 56 
 57     public async Task ImportDataTableAsync(DataTable datatable)
 58     {
 59       var mapping = await this.mappingservice.Queryable()
 60                         .Where(x => x.EntitySetName == "Company" &&
 61                            (x.IsEnabled == true || (x.IsEnabled == false && x.DefaultValue != null))
 62                            ).ToListAsync();
 63       if (mapping.Count == 0)
 64       {
 65         throw new  NullReferenceException("没有找到Work对象的Excel导入配置信息,请执行[系统管理/Excel导入配置]");
 66       }
 67       foreach (DataRow row in datatable.Rows)
 68       {
 69 
 70         var requiredfield = mapping.Where(x => x.IsRequired == true && x.IsEnabled == true && x.DefaultValue == null).FirstOrDefault()?.SourceFieldName;
 71         if (requiredfield != null || !row.IsNull(requiredfield))
 72         {
 73           var item = new Company();
 74           foreach (var field in mapping)
 75           {
 76             var defval = field.DefaultValue;
 77             var contain = datatable.Columns.Contains(field.SourceFieldName ?? "");
 78             if (contain && !row.IsNull(field.SourceFieldName))
 79             {
 80               var worktype = item.GetType();
 81               var propertyInfo = worktype.GetProperty(field.FieldName);
 82               var safetype = Nullable.GetUnderlyingType(propertyInfo.PropertyType) ?? propertyInfo.PropertyType;
 83               var safeValue = (row[field.SourceFieldName] == null) ? null : Convert.ChangeType(row[field.SourceFieldName], safetype);
 84               propertyInfo.SetValue(item, safeValue, null);
 85             }
 86             else if (!string.IsNullOrEmpty(defval))
 87             {
 88               var worktype = item.GetType();
 89               var propertyInfo = worktype.GetProperty(field.FieldName);
 90               if (string.Equals(defval, "now", StringComparison.OrdinalIgnoreCase) && (propertyInfo.PropertyType == typeof(DateTime) || propertyInfo.PropertyType == typeof(Nullable)))
 91               {
 92                 var safetype = Nullable.GetUnderlyingType(propertyInfo.PropertyType) ?? propertyInfo.PropertyType;
 93                 var safeValue = Convert.ChangeType(DateTime.Now, safetype);
 94                 propertyInfo.SetValue(item, safeValue, null);
 95               }
 96               else if (string.Equals(defval, "guid", StringComparison.OrdinalIgnoreCase))
 97               {
 98                 propertyInfo.SetValue(item, Guid.NewGuid().ToString(), null);
 99               }
100               else if (string.Equals(defval, "user", StringComparison.OrdinalIgnoreCase))
101               {
102                 propertyInfo.SetValue(item, "", null);
103               }
104               else
105               {
106                 var safetype = Nullable.GetUnderlyingType(propertyInfo.PropertyType) ?? propertyInfo.PropertyType;
107                 var safeValue = Convert.ChangeType(defval, safetype);
108                 propertyInfo.SetValue(item, safeValue, null);
109               }
110             }
111           }
112           this.Insert(item);
113         }
114       }
115     }
116 
117     // Example, adding synchronous Single method
118     public Company Single(Expressionbool>> predicate)
119     {
120       
121       return this.Repository.Queryable().Single(predicate);
122 
123     }
124   }
125 }
View Code
  • 添加Controller

MVC Controller

  1 namespace SmartAdmin.WebUI.Controllers
  2 {
  3   public class CompaniesController : Controller
  4   {
  5     private  readonly ICompanyService companyService;
  6     private readonly IUnitOfWork unitOfWork;
  7     private readonly ILogger _logger;
  8     private readonly IWebHostEnvironment _webHostEnvironment;
  9     public CompaniesController(ICompanyService companyService,
 10           IUnitOfWork unitOfWork,
 11           IWebHostEnvironment webHostEnvironment,
 12           ILogger logger)
 13     {
 14       this.companyService = companyService;
 15       this.unitOfWork = unitOfWork;
 16       this._logger = logger;
 17       this._webHostEnvironment = webHostEnvironment;
 18     }
 19 
 20     // GET: Companies
 21     public IActionResult Index()=> View();
 22     //datagrid 数据源
 23     public async Task GetData(int page = 1, int rows = 10, string sort = "Id", string order = "asc", string filterRules = "")
 24     {
 25       try
 26       {
 27         var filters = PredicateBuilder.FromFilter(filterRules);
 28         var total = await this.companyService
 29                              .Query(filters)
 30                              .AsNoTracking()
 31                              .CountAsync()
 32                               ;
 33         var pagerows = (await this.companyService
 34                              .Query(filters)
 35                               .AsNoTracking()
 36                            .OrderBy(n => n.OrderBy(sort, order))
 37                            .Skip(page - 1).Take(rows)
 38                            .SelectAsync())
 39                            .Select(n => new
 40                            {
 41                              Id = n.Id,
 42                              Name = n.Name,
 43                              Code = n.Code,
 44                              Address = n.Address,
 45                              Contect = n.Contect,
 46                              PhoneNumber = n.PhoneNumber,
 47                              RegisterDate = n.RegisterDate.ToString("yyyy-MM-dd HH:mm:ss")
 48                            }).ToList();
 49         var pagelist = new { total = total, rows = pagerows };
 50         return Json(pagelist);
 51       }
 52       catch(Exception e) {
 53         throw e;
 54         }
 55 
 56     }
 57     //编辑 
 58     [HttpPost]
 59     [ValidateAntiForgeryToken]
 60     public async Task Edit(Company company)
 61     {
 62       if (ModelState.IsValid)
 63       {
 64         try
 65         {
 66           this.companyService.Update(company);
 67 
 68           var result = await this.unitOfWork.SaveChangesAsync();
 69           return Json(new { success = true, result = result });
 70         }
 71          catch (Exception e)
 72         {
 73           return Json(new { success = false, err = e.GetBaseException().Message });
 74         }
 75       }
 76       else
 77       {
 78         var modelStateErrors = string.Join(",", this.ModelState.Keys.SelectMany(key => this.ModelState[key].Errors.Select(n => n.ErrorMessage)));
 79         return Json(new { success = false, err = modelStateErrors });
 80         //DisplayErrorMessage(modelStateErrors);
 81       }
 82       //return View(work);
 83     }
 84     //新建
 85     [HttpPost]
 86     [ValidateAntiForgeryToken]
 87    
 88     public async Task Create([Bind("Name,Code,Address,Contect,PhoneNumber,RegisterDate")] Company company)
 89     {
 90       if (ModelState.IsValid)
 91       {
 92         try
 93         {
 94           this.companyService.Insert(company);
 95        await this.unitOfWork.SaveChangesAsync();
 96           return Json(new { success = true});
 97         }
 98         catch (Exception e)
 99         {
100           return Json(new { success = false, err = e.GetBaseException().Message });
101         }
102 
103         //DisplaySuccessMessage("Has update a Work record");
104         //return RedirectToAction("Index");
105       }
106       else
107        {
108         var modelStateErrors = string.Join(",", this.ModelState.Keys.SelectMany(key => this.ModelState[key].Errors.Select(n => n.ErrorMessage)));
109         return Json(new { success = false, err = modelStateErrors });
110         //DisplayErrorMessage(modelStateErrors);
111       }
112       //return View(work);
113     }
114     //删除当前记录
115     //GET: Companies/Delete/:id
116     [HttpGet]
117     public async Task Delete(int id)
118     {
119       try
120       {
121         await this.companyService.DeleteAsync(id);
122         await this.unitOfWork.SaveChangesAsync();
123         return Json(new { success = true });
124       }
125      
126       catch (Exception e)
127       {
128         return Json(new { success = false, err = e.GetBaseException().Message });
129       }
130     }
131     //删除选中的记录
132     [HttpPost]
133     public async Task DeleteChecked(int[] id)
134     {
135       try
136       {
137         foreach (var key in id)
138         {
139           await this.companyService.DeleteAsync(key);
140         }
141         await this.unitOfWork.SaveChangesAsync();
142         return Json(new { success = true });
143       }
144       catch (Exception e)
145       {
146         return Json(new { success = false, err = e.GetBaseException().Message });
147       }
148     }
149     //保存datagrid编辑的数据
150     [HttpPost]
151     public async Task AcceptChanges(Company[] companies)
152     {
153       if (ModelState.IsValid)
154       {
155         try
156         {
157           foreach (var item in companies)
158           {
159             this.companyService.ApplyChanges(item);
160           }
161           var result = await this.unitOfWork.SaveChangesAsync();
162           return Json(new { success = true, result });
163         }
164         catch (Exception e)
165         {
166           return Json(new { success = false, err = e.GetBaseException().Message });
167         }
168       }
169       else
170       {
171         var modelStateErrors = string.Join(",", ModelState.Keys.SelectMany(key => ModelState[key].Errors.Select(n => n.ErrorMessage)));
172         return Json(new { success = false, err = modelStateErrors });
173       }
174 
175     }
176     //导出Excel
177     [HttpPost]
178     public async Task ExportExcel(string filterRules = "", string sort = "Id", string order = "asc")
179     {
180       var fileName = "compnay" + DateTime.Now.ToString("yyyyMMddHHmmss") + ".xlsx";
181       var stream = await this.companyService.ExportExcelAsync(filterRules, sort, order);
182       return File(stream, "application/vnd.ms-excel", fileName);
183     }
184     //导入excel
185     [HttpPost]
186     public async Task ImportExcel()
187     {
188       try
189       {
190         var watch = new Stopwatch();
191         watch.Start();
192         var total = 0;
193         if (Request.Form.Files.Count > 0)
194         {
195           for (var i = 0; i < this.Request.Form.Files.Count; i++)
196           {
197             var model = Request.Form["model"].FirstOrDefault() ?? "company";
198             var folder = Request.Form["folder"].FirstOrDefault() ?? "company";
199             var autosave = Convert.ToBoolean(Request.Form["autosave"].FirstOrDefault());
200             var properties = (Request.Form["properties"].FirstOrDefault()?.Split(','));
201             var file = Request.Form.Files[i];
202             var filename = file.FileName;
203             var contenttype = file.ContentType;
204             var size = file.Length;
205             var ext = Path.GetExtension(filename);
206             var path = Path.Combine(this._webHostEnvironment.ContentRootPath, "UploadFiles", folder);
207             if (!Directory.Exists(path))
208             {
209               Directory.CreateDirectory(path);
210             }
211             var datatable = await NPOIHelper.GetDataTableFromExcelAsync(file.OpenReadStream(), ext);
212             await this.companyService.ImportDataTableAsync(datatable);
213             await this.unitOfWork.SaveChangesAsync();
214             total = datatable.Rows.Count;
215             if (autosave)
216             {
217               var filepath = Path.Combine(path, filename);
218               file.OpenReadStream().Position = 0;
219 
220               using (var stream = System.IO.File.Create(filepath))
221               {
222                 await file.CopyToAsync(stream);
223               }
224             }
225 
226           }
227         }
228         watch.Stop();
229         return Json(new { success = true, total = total, elapsedTime = watch.ElapsedMilliseconds });
230       }
231       catch (Exception e) {
232         this._logger.LogError(e, "Excel导入失败");
233         return this.Json(new { success = false,  err = e.GetBaseException().Message });
234       }
235         }
236     //下载模板
237     public async Task Download(string file) {
238       
239       this.Response.Cookies.Append("fileDownload", "true");
240       var path = Path.Combine(this._webHostEnvironment.ContentRootPath, file);
241       var downloadFile = new FileInfo(path);
242       if (downloadFile.Exists)
243       {
244        var fileName = downloadFile.Name;
245        var mimeType = MimeTypeConvert.FromExtension(downloadFile.Extension);
246        var fileContent = new byte[Convert.ToInt32(downloadFile.Length)];
247         using (var fs = downloadFile.Open(FileMode.Open, FileAccess.Read, FileShare.Read))
248         {
249           await fs.ReadAsync(fileContent, 0, Convert.ToInt32(downloadFile.Length));
250         }
251         return this.File(fileContent, mimeType, fileName);
252       }
253       else
254       {
255         throw new FileNotFoundException($"文件 {file} 不存在!");
256       }
257     }
258 
259     }
260 }
View Code
  • 新建 View

MVC Views\Companies\Index

  1 @model SmartAdmin.Data.Models.Company
  2 @{
  3   ViewData["Title"] = "企业信息";
  4   ViewData["PageName"] = "Companies_Index";
  5   ViewData["Heading"] = " 企业信息";
  6   ViewData["Category1"] = "组织架构";
  7   ViewData["PageDescription"] = "";
  8 }
  9 
class="row"> 10
class="col-lg-12 col-xl-12"> 11
"panel-1" class="panel"> 12
class="panel-hdr"> 13

14 公司信息 15

16
class="panel-toolbar"> 17 18 19
20 21
22
class="panel-container show"> 23
class="panel-content py-2 rounded-bottom border-faded border-left-0 border-right-0 text-muted bg-subtlelight-fade "> 24
class="row no-gutters align-items-center"> 25
class="col"> 26 27
class="btn-group btn-group-sm"> 28 31
32
class="btn-group btn-group-sm"> 33 36
37
class="btn-group btn-group-sm"> 38 41
42
class="btn-group btn-group-sm"> 43 46
47
class="btn-group btn-group-sm"> 48 49 52 57
58
class="btn-group btn-group-sm hidden-xs"> 59 60 63 68
69
class="btn-group btn-group-sm hidden-xs"> 70 73
74 75
76 77
78 79
80
class="panel-content"> 81
class="table-responsive"> 82 "companies_datagrid"> 83
84
85
86
87
88
89
90 91
"companydetailwindow" class="easyui-window" 92 title="明细数据" 93 data-options="modal:true, 94 closed:true, 95 minimizable:false, 96 collapsible:false, 97 maximized:false, 98 iconCls:'fal fa-window', 99 onBeforeClose:function(){ 100 var that = $(this); 101 if(companyhasmodified()){ 102 $.messager.confirm('确认','你确定要放弃保存修改的记录?',function(r){ 103 if (r){ 104 var opts = that.panel('options'); 105 var onBeforeClose = opts.onBeforeClose; 106 opts.onBeforeClose = function(){}; 107 that.panel('close'); 108 opts.onBeforeClose = onBeforeClose; 109 hook = false; 110 } 111 }); 112 return false; 113 } 114 }, 115 onOpen:function(){ 116 $(this).window('vcenter'); 117 $(this).window('hcenter'); 118 }, 119 onRestore:function(){ 120 }, 121 onMaximize:function(){ 122 } 123 " style="width:820px;height:420px;display:none"> 124 125
class="panel-content py-2 rounded-bottom border-faded border-left-0 border-right-0 text-muted bg-subtlelight-fade sticky-top"> 126
class="d-flex flex-row-reverse pr-4"> 127
class="btn-group btn-group-sm mr-1"> 128 131
132
class="btn-group btn-group-sm mr-1" id="deleteitem-btn-group"> 133 136
137
138
139
class="panel-container show"> 140
class="container"> 141
class="panel-content"> 142
"company_form" 143 class="easyui-form form-horizontal p-1" 144 method="post" 145 data-options="novalidate:true, 146 onChange: function(target){ 147 hook = true; 148 $('button[name*=\'saveitembutton\']').prop('disabled', false); 149 }, 150 onLoadSuccess:function(data){ 151 hook = false; 152 $('button[name*=\'saveitembutton\']').prop('disabled', true); 153 }"> 154 @Html.AntiForgeryToken() 155 156 @Html.HiddenFor(model => model.Id) 157
class="form-group"> 158 159 160
class="row h-100 justify-content-center align-items-center"> 161 162
class="col-md-4 mb-1 pl-1"> 163 "@Html.IdFor(model => model.Name)" 164 name="@Html.NameFor(model => model.Name)" 165 value="@Html.ValueFor(model => model.Name)" 166 tabindex="0" required 167 class="easyui-textbox" 168 style="width:100%" 169 type="text" 170 data-options="prompt:'@Html.DescriptionFor(model => model.Name)', 171 required:true, 172 validType: 'length[0,50]' 173 " /> 174
175 176
class="col-md-4 mb-1 pl-1"> 177 "@Html.IdFor(model => model.Code)" 178 name="@Html.NameFor(model => model.Code)" 179 value="@Html.ValueFor(model => model.Code)" 180 tabindex="1" required 181 class="easyui-textbox" 182 style="width:100%" 183 type="text" 184 data-options="prompt:'@Html.DescriptionFor(model => model.Code)', 185 required:true, 186 validType: 'length[0,12]' 187 " /> 188
189 190
class="col-md-4 mb-1 pl-1"> 191 "@Html.IdFor(model => model.Address)" 192 name="@Html.NameFor(model => model.Address)" 193 value="@Html.ValueFor(model => model.Address)" 194 tabindex="2" 195 class="easyui-textbox" 196 style="width:100%" 197 type="text" 198 data-options="prompt:'@Html.DescriptionFor(model => model.Address)', 199 required:false, 200 validType: 'length[0,50]' 201 " /> 202
203 204
class="col-md-4 mb-1 pl-1"> 205 "@Html.IdFor(model => model.Contect)" 206 name="@Html.NameFor(model => model.Contect)" 207 value="@Html.ValueFor(model => model.Contect)" 208 tabindex="3" 209 class="easyui-textbox" 210 style="width:100%" 211 type="text" 212 data-options="prompt:'@Html.DescriptionFor(model => model.Contect)', 213 required:false, 214 validType: 'length[0,12]' 215 " /> 216
217 218
class="col-md-4 mb-1 pl-1"> 219 "@Html.IdFor(model => model.PhoneNumber)" 220 name="@Html.NameFor(model => model.PhoneNumber)" 221 value="@Html.ValueFor(model => model.PhoneNumber)" 222 tabindex="4" 223 class="easyui-textbox" 224 style="width:100%" 225 type="text" 226 data-options="prompt:'@Html.DescriptionFor(model => model.PhoneNumber)', 227 required:false, 228 validType: 'length[0,20]' 229 " /> 230
231 232
class="col-md-4 mb-1 pl-1"> 233 "@Html.IdFor(model => model.RegisterDate)" 234 name="@Html.NameFor(model => model.RegisterDate)" 235 value="@Html.ValueFor(model => model.RegisterDate)" 236 tabindex="5" required 237 class="easyui-datebox" 238 style="width:100%" 239 type="text" 240 data-options="prompt:'@Html.DescriptionFor(model => model.RegisterDate)', 241 required:true, 242 formatter:dateformatter" /> 243
244
245
246
247
248
249
250
251 252 253 @await Component.InvokeAsync("ImportExcel", new ImportExcelOptions { entity="Company", 254 folder="Companies", 255 url="/Companies/ImportExcel", 256 tpl="/Companies/Download" 257 258 259 }) 260 261 @section HeadBlock { 262 "~/css/notifications/toastr/toastr.css" rel="stylesheet" asp-append-version="true" /> 263 "~/css/formplugins/bootstrap-daterangepicker/bootstrap-daterangepicker.css" rel="stylesheet" asp-append-version="true" /> 264 "~/js/easyui/themes/insdep/easyui.css" rel="stylesheet" asp-append-version="true" /> 265 } 266 @section ScriptsBlock { 267 268 269 270 271 272 273 274 275 276 277 278 279 280 688 785 }
View Code

 

上面View层的代码非常的复杂,但都是固定格式,可以用scaffold快速生成

  • 配置依赖注入(DI),注册服务

打开 startup.cs 在 public void ConfigureServices(IServiceCollection services) 注册服务 services.AddScoped(); 
services.AddScoped();

  • 更新数据库

EF Core Code-First 同步更新数据库 
在 Visual Studio.Net 
Package Manager Controle 运行 
PM>:add-migration create_Company 
PM>:update-database 
PM>:更新完成

  • Debug 运行项目 

高级应用

CAP 分布式事务的解决方案及应用场景 
nuget 安装组件 
PM> Install-Package DotNetCore.CAP 
PM> Install-Package DotNetCore.CAP.RabbitMQ 
PM> Install-Package DotNetCore.CAP.SqlServer \

  • 配置Startup.cs
 1 public void ConfigureServices(IServiceCollection services)
 2     {
 3       services.AddCap(x =>
 4       {
 5         x.UseEntityFramework();
 6         x.UseRabbitMQ("127.0.0.1");
 7         x.UseDashboard();
 8         x.FailedRetryCount = 5;
 9         x.FailedThresholdCallback = failed =>
10         {
11           var logger = failed.ServiceProvider.GetService>();
12           logger.LogError($@"A message of type {failed.MessageType} failed after executing {x.FailedRetryCount} several times, 
13                         requiring manual troubleshooting. Message name: {failed.Message.GetName()}");
14         };
15       });
16     }
View Code

 

  • 发布消息
  • 订阅消息

roadmap

  • 完善主要的开发文档
  • 支持My SQL数据库
  • 还会继续重构和完善代码
  • 开发Scaffold MVC模板,生成定制化的Controller 和 View 减少开发人员重复工作
  • 完善授权访问策略(policy-based authorization)
  • 开发Visual Sutdio.net代码生成插件(类似国内做比较好的52abp)

我的联系方式,qq群,赞助二维码

你可能感兴趣的:(基于领域驱动设计(DDD)超轻量级快速开发架构)