企业级工作流解决方案(十五)--集成Abp和ng-alain--Abp其他改造
配置功能增强
Abp定义了各种配置接口,但是没有定义这些配置数据从哪里来,但是管理配置数据对于一个应用程序来说,是必不可少的一件事情。
.net的配置数据管理,一般放在Web.config文件或者App.config文件里面,.net core也是定义在一个json的配置文件里面。我们也可以自定义configSection,但是对于稍微大型一点的应用,配置可能非常的复杂,代码比较规范的,对于每一个配置或者配置组都写好注释,但是毕竟是比较麻烦的一件事情。
我们可以对所有的配置进行集中的管理,把所有的配置放到一个固定的应用程序目录下面,每一个功能模块定义一个配置文件,配置文件名称做为分组名称,比如我们可以放到应用的Configuration/AppSettings目录下,配置文件格式如下:
那么我们可以在应用启动的时候,读取此目录下的所有文件,放到全局静态的字典里面,那么我们调用的时候,只需要调用静态方法,传递分组和key即可,比如:AppSettingManager.GetSetting("WorkflowNotice","SignalrTaskTitle"),代码比较简单,实现如下:
public static class AppSettingManager { #region Private Memeber private static Dictionary<string, Dictionary<string, string>> dictAppsettings; static AppSettingManager() { dictAppsettings = new Dictionary<string, Dictionary<string, string>>(); } private static string BaseFolderPath { get { return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Configuration/AppSettings"); } } private static Dictionary<string, string> LoadSettings(string filePath) { if (!File.Exists(filePath)) { return new Dictionary<string, string>(0); } Dictionary<string, string> rst = new Dictionary<string, string>(); XElement doc = XElement.Load(filePath); foreach (var x in doc.Descendants("add")) { if (x.Attribute("key") == null || x.Attribute("key").Value == null || x.Attribute("key").Value.Trim().Length <= 0) { throw new ApplicationException("配置文件格式有错误,存在add节点,但是没有key属性,路径为: " + filePath + ", 请检查!"); } string key = x.Attribute("key").Value.Trim().ToUpper(); if (rst.ContainsKey(key)) { throw new ApplicationException($"配置文件存在相同的Key[{x.Attribute("key").Value.Trim()}],路径为:{filePath},请检查!"); } string value = x.Attribute("value") == null ? null : (x.Attribute("value").Value == null ? null : x.Attribute("value").Value.Trim()); value = value ?? x.Value; rst.Add(key, value); } return rst; } #endregion public static void InitAppsettings() { var strFileInfos = Directory.GetFiles(BaseFolderPath, "*.config"); foreach(var strFileInfo in strFileInfos) { var fileName = Path.GetFileNameWithoutExtension(strFileInfo); var fileNameAppsetting = LoadSettings(strFileInfo); dictAppsettings.Add(fileName, fileNameAppsetting); } } public static string GetSetting(string fileTitle, string key) { if(!dictAppsettings.ContainsKey(fileTitle)) { return ""; } key = key.ToUpper(); if(!dictAppsettings[fileTitle].ContainsKey(key)) { return ""; } return dictAppsettings[fileTitle][key]; } }
还有另外一种情况,我们定义了配置接口,比如abp定义了各种配置接口,但是配置数据也需要从其他地方读取出来,常见的方式也是配置文件,这种情况,我们可以定义Xml与配置实现的映射,启动的时候读取每一个文件映射到接口配置实现类。
配置帮助类:
public class XmlConfigProvider { public static T GetConfig(string fileName, string relativePath = "") { if(string.IsNullOrEmpty(relativePath)) { relativePath = @"Configuration\XmlConfig"; } string fileFullName = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, relativePath, fileName); if(!File.Exists(fileFullName)) { return default(T); } return LoadFromXml (fileFullName); } private static T LoadFromXml (string filePath) { FileStream fs = null; try { XmlSerializer serializer = new XmlSerializer(typeof(T)); fs = new FileStream(filePath, FileMode.Open, FileAccess.Read); return (T)serializer.Deserialize(fs); } finally { if (fs != null) { fs.Close(); fs.Dispose(); } } } }
配置实现类举例:
[Serializable] [XmlRoot] public class SocketServiceConfiguration : ISocketServiceConfiguration { [XmlAttribute] public int Backlog { get; set; } ////// 客户端解码类型 /// [XmlAttribute] public EMessageCode MessageCode { get; set; } /// /// 端口 /// [XmlAttribute] public int Port { get; set; } [XmlAttribute] public bool Libuv { get; set; } public SocketServiceConfiguration() { Backlog = 100; Libuv = false; MessageCode = EMessageCode.MessagePack; } }
使用,可以在Module里面读取
// 注册客户端配置,固定从Xml文件读取 SocketServiceConfiguration socketServiceConfiguration = XmlConfigProvider.GetConfig("SocketServiceConfiguration.xml"); IocManager.IocContainer.Register( Component .For () .Instance(socketServiceConfiguration) );
T4模版,部分代码自动生成
有很多方便的代码生成工具,好不好用我就不说了,只要能够提高我们平时的编码效率,减少一些繁琐的基础编码工作,对我们有帮助,总归是好的。
T4模版,直接在VS里面,编辑之后保存,就会根据模版生成对应的代码文件,我们可以自定义代码生成规则,完全的自定义,可见即可得。
我这里的配置,是围绕着一个Xml文件,内容是对一个实体信息集合的描述,Xml文件格式如下:
<FrameworkTemplates> <FrameworkTemplate Name="WF_FlowType" Type="DomainEntity" DataTableName="WF_FlowType" Inherit="AuditedEntity<Guid>, IMayHaveTenant"> <TemplateItem> <Field>IdField> <CnName>主键CnName> <Type>guidType> <IsRequred>falseIsRequred> TemplateItem> <TemplateItem> <Field>TypeNameField> <CnName>类型名称CnName> <Type>stringType> <IsRequred>trueIsRequred> <MaxLength>50MaxLength> TemplateItem> <TemplateItem> <Field>ParentIdField> <CnName>父节点IdCnName> <Type>guidType> <IsRequred>falseIsRequred> TemplateItem> <TemplateItem> <Field>NotesField> <CnName>备注CnName> <Type>stringType> <IsRequred>falseIsRequred> <MaxLength>500MaxLength> TemplateItem> FrameworkTemplate> …… FrameworkTemplates>
那个我们可以根据这个Xml文件的描述,生成DbContext、DomainEntity、CreateDto、UpdateDto、ApplicationService等公共的信息,每一个类都需要标识为partial,方便我们编写非公共的部分代码。生成DomainEntity代码举例:
<#@ template language="C#" hostSpecific="true" #> <#@ output extension=".cs" #> <#@ assembly name="System.Xml"#> <#@ import namespace="System.Xml" #> <#@ assembly name="EnvDTE" #> <#@ import namespace="Microsoft.VisualStudio.TextTemplating" #> <#@ include file="$(SolutionDir)\Resources\T4\TemplateFilemanager.CS.ttinclude" #> <# var manager = TemplateFileManager.Create(this); string strProjectName = "Workflow"; EnvDTE.DTE dte = (EnvDTE.DTE) ((IServiceProvider) this.Host).GetService(typeof(EnvDTE.DTE)); XmlDocument doc = new XmlDocument(); var strSolutionPath = System.IO.Path.GetDirectoryName(dte.Solution.FullName); var strTemplateFilePath = System.IO.Path.Combine(strSolutionPath, @"Resources\Modules\Workflow.xml"); doc.Load(strTemplateFilePath); var frameworkTemplateNodes = doc.SelectNodes("/FrameworkTemplates/FrameworkTemplate[@Type='DomainEntity']"); foreach (XmlNode templateNode in frameworkTemplateNodes) { manager.StartNewFile(templateNode.Attributes["Name"].Value + ".cs"); var inheritNode = templateNode.Attributes["Inherit"]; var strInherit = ""; #> using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Text; using CK.Sprite.Domain.Entities; using CK.Sprite.Domain.Entities.Auditing; using CK.Workflow.DomainInterface; namespace CK.<#= strProjectName #>.DomainEntity { <# if(inheritNode==null || inheritNode.Value=="") // ---输出Class--- { #> public partial class <#= templateNode.Attributes["Name"].Value #> <# } else { strInherit = templateNode.Attributes["Inherit"].Value; #> public partial class <#= templateNode.Attributes["Name"].Value #> : <#= strInherit #> <# } // ---End输出Class--- #> { <# foreach (XmlNode itemNode in templateNode.ChildNodes) { string tempTypeName = ""; string tempFieldName = ""; string tempFieldCnName = ""; string tempIsRequred = ""; string tempMaxLength = ""; string strIsRequred = ""; string strMaxLength = ""; if (itemNode.LocalName == "TemplateItem") { tempFieldName = itemNode.ChildNodes[0].InnerText; tempFieldCnName = itemNode.ChildNodes[1].InnerText; tempTypeName = itemNode.ChildNodes[2].InnerText; tempIsRequred = itemNode.ChildNodes[3].InnerText; for(var i=0;i< itemNode.ChildNodes.Count;i++ ) { if(itemNode.ChildNodes[i].Name == "MaxLength") { tempMaxLength = itemNode.ChildNodes[i].InnerText; } } if(tempFieldName == "Id") { continue; } if(tempIsRequred.ToLower()=="true") { strIsRequred = "[Required]"; } if (!string.IsNullOrEmpty(tempMaxLength) && tempMaxLength != "0") { strMaxLength = "[StringLength("+ tempMaxLength + ")]"; } switch (tempTypeName) { case "int": if (tempIsRequred.ToLower() == "false") { tempTypeName = "int?"; } break; case "double": if (tempIsRequred.ToLower() == "false") { tempTypeName = "double?"; } break; case "date": case "datetime": if (tempIsRequred.ToLower() == "false") { tempTypeName = "DateTime?"; } else { tempTypeName = "DateTime"; } break; case "guid": if (tempIsRequred.ToLower() == "false") { tempTypeName = "Guid?"; } else { tempTypeName = "Guid"; } break; } #> ////// <#= tempFieldCnName #> /// <#= strIsRequred #> <#= strMaxLength #> public <#= tempTypeName #> <#= tempFieldName #> { get; set; } <# } } if (!string.IsNullOrEmpty(strInherit)) { ListautoFields = new List (); var inheritList = strInherit.Replace(" ","").Split(new char[] { ',' }); foreach (var inherit in inheritList) { switch (inherit) { case "IMayHaveTenant": autoFields.Add("public int? TenantId { get; set; }"); break; case "IMustHaveTenant": autoFields.Add("public int TenantId { get; set; }"); break; case "IPassivable": autoFields.Add("public bool IsActive { get; set; }"); break; case "IHasCreationTime": autoFields.Add("public DateTime CreationTime { get; set; }"); break; case "ICreationAudited": autoFields.Add("public DateTime CreationTime { get; set; }"); autoFields.Add("public long? CreatorUserId { get; set; }"); break; case "IHasModificationTime": autoFields.Add("public DateTime? LastModificationTime { get; set; }"); break; case "IModificationAudited": autoFields.Add("public DateTime? LastModificationTime { get; set; }"); autoFields.Add("public long? LastModifierUserId { get; set; }"); break; case "ISoftDelete": autoFields.Add("public bool IsDeleted { get; set; }"); break; case "IHasDeletionTime": autoFields.Add("public DateTime? DeletionTime { get; set; }"); autoFields.Add("public DateTime CreationTime { get; set; }"); break; case "IDeletionAudited": autoFields.Add("public DateTime? DeletionTime { get; set; }"); autoFields.Add("public long? DeleterUserId { get; set; }"); autoFields.Add("public DateTime CreationTime { get; set; }"); break; case "IAudited": autoFields.Add("public DateTime? LastModificationTime { get; set; }"); autoFields.Add("public long? LastModifierUserId { get; set; }"); autoFields.Add("public DateTime CreationTime { get; set; }"); autoFields.Add("public long? CreatorUserId { get; set; }"); break; case "IFullAudited": autoFields.Add("public bool IsDeleted { get; set; }"); autoFields.Add("public long? DeleterUserId { get; set; }"); autoFields.Add("public DateTime? DeletionTime { get; set; }"); autoFields.Add("public DateTime? LastModificationTime { get; set; }"); autoFields.Add("public long? LastModifierUserId { get; set; }"); autoFields.Add("public DateTime CreationTime { get; set; }"); autoFields.Add("public long? CreatorUserId { get; set; }"); break; } } autoFields = autoFields.Distinct().ToList(); foreach(var autoField in autoFields) { #> <#= autoField #> <# } } #> } } <# } manager.Process(); #>
另外,我们还可以定义前端的List和Form页面,甚至不用编写前端代码都可以。
对于Abp的改造还有其他一些地方,比如Signalr的消息通知、Application是否登录权限验证等,相对来说比较简单,就不描述了。
前面两个大的章节的内容就告一段落了,这些是做为整体工作流引擎的框架依赖,所以写在前面,这部分内容是脱离工作流独立存在的,现在在构思工作流部分的内容,工作流部分可能需要对WWF有所了解的朋友才更加容易理解,但是做为研发流程引擎的朋友应该也可以提供一些思路,问题可以直接留言或者QQ联系我:523477776
到目前为止还没有人进行过评论,也比较影响写文章的激情,不知道是否写得有问题,如果有问题对其他人造成错误的引导,那就提前说一声抱歉了。