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 网站
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) 通常实体就是映射到关系数据库中的表,这里说名一下最佳做法和惯例:
- 在域层定义:本项目就是(SmartAdmin.Entity.csproj)
- 继承一个基类 Entity,添加必要审计类比如:创建时间,最后修改时间等
- 必须要有一个主键最好是GRUID(不推荐复合主键),但本项目使用递增的int类型
- 字段不要过多的冗余,可以通过定义关联关系
- 字段属性和方法尽量使用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 DbSetCompanies { get; set; }
- 添加服务对象 Service
在项目 SmartAdmin.Service.csproj 中添加ICompanyService.cs,CompanyService.cs 就是用来实现业务需求 用例的地方
1 //ICompany.cs 2 //根据实际业务用例来创建方法,默认的CRUD,增删改查不需要再定义 3 namespace SmartAdmin.Service 4 { 5 // Example: extending IServiceand/or ITrackableRepository 6 public interface ICompanyService : IService, scope: ICustomerService 7 { 8 // Example: adding synchronous Single method, scope: ICustomerService 9 Company Single(Expression bool>> 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(Expression bool>> predicate) 119 { 120 121 return this.Repository.Queryable().Single(predicate); 122 123 } 124 } 125 }
- 添加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
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 } 9class="row"> 1090 91class="col-lg-12 col-xl-12"> 1189"panel-1" class="panel"> 1288class="panel-hdr"> 132214 公司信息 15
16class="panel-toolbar"> 17 18 1920 21class="panel-container show"> 2387class="panel-content py-2 rounded-bottom border-faded border-left-0 border-right-0 text-muted bg-subtlelight-fade "> 2480class="row no-gutters align-items-center"> 2578 79class="col"> 26 2776 77class="btn-group btn-group-sm"> 28 3132class="btn-group btn-group-sm"> 33 3637class="btn-group btn-group-sm"> 38 4142class="btn-group btn-group-sm"> 43 4647class="btn-group btn-group-sm"> 48 49 5258class="dropdown-menu dropdown-menu-animated"> 53 class="dropdown-item js-waves-on" href="javascript:void()"> 我的记录 5457class="dropdown-divider">55 class="dropdown-item js-waves-on" href="javascript:void()"> 自定义查询 56class="btn-group btn-group-sm hidden-xs"> 59 60 6369class="dropdown-menu dropdown-menu-animated"> 64 class="dropdown-item js-waves-on" href="javascript:importExcel.downloadtemplate()"> 65 class="fal fa-download"> 下载模板 66 6768class="btn-group btn-group-sm hidden-xs"> 70 7374 75class="panel-content"> 8186class="table-responsive"> 8285"companies_datagrid"> 83
84"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 125251 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 }class="panel-content py-2 rounded-bottom border-faded border-left-0 border-right-0 text-muted bg-subtlelight-fade sticky-top"> 126139class="d-flex flex-row-reverse pr-4"> 127138class="btn-group btn-group-sm mr-1"> 128 131132class="btn-group btn-group-sm mr-1" id="deleteitem-btn-group"> 133 136137class="panel-container show"> 140250class="container"> 141249class="panel-content"> 142 247248
上面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>:更新完成
高级应用
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 }
- 发布消息
- 订阅消息
roadmap
- 完善主要的开发文档
- 支持My SQL数据库
- 还会继续重构和完善代码
- 开发Scaffold MVC模板,生成定制化的Controller 和 View 减少开发人员重复工作
- 完善授权访问策略(policy-based authorization)
- 开发Visual Sutdio.net代码生成插件(类似国内做比较好的52abp)