配置功能增强

  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<T>(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<T>(fileFullName);
} private static T LoadFromXml<T>(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; }
/// <summary>
/// 客户端解码类型
/// </summary>
[XmlAttribute]
public EMessageCode MessageCode { get; set; }
/// <summary>
/// 端口
/// </summary>
[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>("SocketServiceConfiguration.xml");
IocManager.IocContainer.Register(
Component
.For<ISocketServiceConfiguration>()
.Instance(socketServiceConfiguration)
);

T4模版,部分代码自动生成

  有很多方便的代码生成工具,好不好用我就不说了,只要能够提高我们平时的编码效率,减少一些繁琐的基础编码工作,对我们有帮助,总归是好的。

  T4模版,直接在VS里面,编辑之后保存,就会根据模版生成对应的代码文件,我们可以自定义代码生成规则,完全的自定义,可见即可得。

  我这里的配置,是围绕着一个Xml文件,内容是对一个实体信息集合的描述,Xml文件格式如下:

<FrameworkTemplates>
<FrameworkTemplate Name="WF_FlowType" Type="DomainEntity" DataTableName="WF_FlowType" Inherit="AuditedEntity&lt;Guid&gt;, IMayHaveTenant">
<TemplateItem>
<Field>Id</Field>
<CnName>主键</CnName>
<Type>guid</Type>
<IsRequred>false</IsRequred>
</TemplateItem>
<TemplateItem>
<Field>TypeName</Field>
<CnName>类型名称</CnName>
<Type>string</Type>
<IsRequred>true</IsRequred>
<MaxLength>50</MaxLength>
</TemplateItem>
<TemplateItem>
<Field>ParentId</Field>
<CnName>父节点Id</CnName>
<Type>guid</Type>
<IsRequred>false</IsRequred>
</TemplateItem>
<TemplateItem>
<Field>Notes</Field>
<CnName>备注</CnName>
<Type>string</Type>
<IsRequred>false</IsRequred>
<MaxLength>500</MaxLength>
</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;
}
#>
/// <summary>
/// <#= tempFieldCnName #>
/// </summary>
<#= strIsRequred #>
<#= strMaxLength #>
public <#= tempTypeName #> <#= tempFieldName #> { get; set; }
<#
}
}
if (!string.IsNullOrEmpty(strInherit))
{
List<string> autoFields = new List<string>();
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

  到目前为止还没有人进行过评论,也比较影响写文章的激情,不知道是否写得有问题,如果有问题对其他人造成错误的引导,那就提前说一声抱歉了。

企业级工作流解决方案(十五)--集成Abp和ng-alain--Abp其他改造的更多相关文章

  1. 企业级工作流解决方案(十四)--集成Abp和ng-alain--自动化脚本

    对于.net方向,做过自动化的,应该没有人不熟悉msbuild吧,非常强大的代码编译工具,.net平台的编译工作都是交给他来完成的,包括.net core的命令,本质上都是调用msbuild来执行的 ...

  2. 企业级工作流解决方案(十)--集成Abp和ng-alain--权限系统

    权限系统 应用系统离不开权限控制,权限中心不一定能抽象出所有的业务场景,这里定义的权限系统不一定能够满足所有的场景,但应该可以满足多数的业务需求. Abp的zero项目也定义了权限相关的表,但里面很多 ...

  3. 企业级工作流解决方案(十二)--集成Abp和ng-alain--用户身份认证与权限验证

    多租户 如果系统需要支持多租户,那么最好事先定义好多租户的存储部署方式,Abp提供了几种方式,根据需要选择,每一个用户身份认证与权限验证都需要完全的隔离 这里设计的权限数据全部存储在缓存中,每个租户单 ...

  4. 企业级工作流解决方案(十三)--集成Abp和ng-alain--数据库读写分离

    说到程序里面数据库管理,无非就是两件事情,一是数据库操作,对于数据库的操作,各种程序语言都有封装,也就是所谓的ORM框架,.net 方向一般用得比较多和就是.net framework和dapper, ...

  5. 企业级工作流解决方案(十一)--集成Abp和ng-alain--权限系统服务

    权限系统主要定义为管理员增删改查权限数据,直接读取数据库,权限系统服务主要定义为供其他系统调用的权限验证接口,定义为两个不同的微服务. 权限系统有一个特点,数据变动比较小,数据量本身并不是很大,访问量 ...

  6. 企业级工作流解决方案(六)--微服务消息处理模型之与Abp集成

    身份认证传递 对于Abp比较熟悉的朋友应该对他里面的用户身份认证比较熟悉,他是通过实现微软提供的权限认证方式实现的,用户登录身份信息存储在System.Security.Claims.ClaimsPr ...

  7. SpringCloud微服务实战——搭建企业级开发框架(十五):集成Sentinel高可用流量管理框架【熔断降级】

      Sentinel除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一.由于调用关系的复杂性,如果调用链路中的某个资源不稳定,最终会导致请求发生堆积.Sentinel ...

  8. SpringBoot入门教程(十五)集成Druid

    Druid是阿里巴巴开源平台上一个数据库连接池实现,它结合了C3P0.DBCP.PROXOOL等DB池的优点,同时加入了日志监控,可以很好的监控DB池连接和SQL的执行情况,可以说是针对监控而生的DB ...

  9. 企业级工作流解决方案(八)--微服务Tcp消息传输模型之服务端处理

    服务端启动 服务端启动主要做几件事情,1. 从配置文件读取服务配置(主要是服务监听端口和编解码配置),2. 注册编解码器工厂,3. 启动dotnetty监听端口,4. 读取配置文件,解析全局消息处理模 ...

随机推荐

  1. git学习(二) git的文件状态

    git的文件状态 用于查看git的状态 git status 用于git文件的删除操作 git rm 如果只是 git rm --cache 仅删除暂存区里的文件: 如果不加--cache 会删除工作 ...

  2. 学python,大概要多久?

    都让开!本人文科生,自学Python 2年半,作为一个曾经完全0基础,啥都不懂纯靠自学学会python的文科生,有一些不成熟的小建议可以分享一下. 首先不要觉着编程难,只要你认识26个英文字母,有一点 ...

  3. Java POST请求案例

    <<<<<<<<<<<<<<<<<<<<<<<<< ...

  4. 群晖DS218+做maven私服(nexus3)

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  5. Linux常用系统文件目录结构

    Linux常用系统文件目录结构 bin:全称binary,含义是二进制.该目录中存储的都是一些二进制文件,文件都是可以被运行的. dev:该目录主要存放的是外接设备,例如硬盘.其他的光盘等.在其中的外 ...

  6. 赛门铁克和DigiCert证书有什么区别?

    在众多国人眼里,赛门铁克Symantec名气更胜于DigiCert证书.但是,我们知道2017年赛门铁克因一系列原因被DigiCert收购,品牌名称也被更新为DigiCert Secure Site. ...

  7. Union-Find算法详解

    今天讲讲 Union-Find 算法,也就是常说的并查集算法,主要是解决图论中「动态连通性」问题的.名词很高端,其实特别好理解,等会解释,另外这个算法的应用都非常有趣. 说起这个 Union-Find ...

  8. C#中的release和debug模式

    以下内容来源:https://www.cnblogs.com/rgjycs/p/9254332.html 在程序调试时的debug和release 网上有如下的描述:Debug 通常称为调试版本,它包 ...

  9. Swagger 3.0 天天刷屏,真的香吗?

    持续原创输出,点击上方蓝字关注我 目录 前言 官方文档如何说? Spring Boot版本说明 添加依赖 springfox-boot-starter做了什么? 撸起袖子就是干? 定制一个基本的文档示 ...

  10. 论文解读 - Relational Pooling for Graph Representations

    1 简介 本文着眼于对Weisfeiler-Lehman算法(WL Test)和WL-GNN模型的分析,针对于WL测试以及WL-GNN所不能解决的环形跳跃连接图(circulant skip link ...