.NET架构小技巧(3)——反射,架构人员法宝I
如题,这是我的心声,反射在我的开发中用的频次还是比较高的,有一本万利的感觉,一段复杂的代码,可以节省大量的时间;但带来的一个问题性能相对较差,所以要选择适合的场景使用。
关于C#中的反射基本用法,这里不作详细介绍,官网有详细的说明。
下面是在网上找了一个医保接口的案件,比如有两个业务接口,033,027,这个接口的传输内容类似xml,但又是有区别的,不是严格意义上的xml格式。
医院就诊卡接口规范(业务:033)
输入参数:
业务编号
开始日期
结束日期
输出参数:
病历编号
HIS端结算流水
医保统筹支付(元)
医保帐户支付(元)
患者个人自付(元)
本次结算总额(元)
结算类别(1消费,0退费)
操作人员编号
操作人员姓名
支付方式
单据编号
单据类别
单据金额(元)
医院就诊卡接口规范(业务:027)
输入参数:
交易码(见上表交易代码)
交易日期(YYYYMMDD)
发票号
交易类别
输出参数:
业务编号
返回值:0 成功,其他失败
错误描述信息
本次交易流水
收费项目编码
收费项目名称
开立科室编码
开立科室名称
开立医生编码
开立医生姓名
开单时间(yyyy-MM-dd HH:mm:ss)
收费时间(yyyy-MM-dd HH:mm:ss)
数量
有效标志
收费人员编号
执行科室编号
单据编号
......
这个医保接口的对接方式入参都是一个Request节点,出参都是Response节点,至少是调用dll,还是通过什么协议发送参数,接收参数,是另外一会事,所以咱们重点来说架构小技巧。
首先思考的是,我们在.net中是面向对应编程,这些接口的输入参数,输出参数应该对应成实体,当然用字符串拼接也可以,但当接口众多时,一点一点拼接,势必会出错率高,同时个性化处理非常麻烦,另一方面,显得比较low。这时,反射大显身手的机会就来了,假如我只要按输入参数,输出参数的属性定义好实体类,再造一个神器,能把实体转成输入参数字符串,也能把输出参数转成实体类,对我们来说,就是完全的OOP了,“辛苦两个转换方法,幸福所有接口”,当然对开发一个完整的医保接口来说,另一个难点是,从现有系统中组织输入参数实体类,和回写输出参数实体,但也不是架构技巧的重点。
还是上代码吧:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Xml;
namespace ArchitectureDemo03
{
class Program
{
static void Main(string[] args)
{
var readCard033 = new ReadCard033
{
TradeCode = "033",
BeginDate = DateTime.Now,
EndDate = "20200102"
};
var readcard033Back = Send(readCard033);
Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(readcard033Back));
var readCard027 = new ReadCard027
{
Date = "20201212",
InvoiceNo = "abcd",
Time = "121212",
TradeCode = "003",
TransType = "123"
};
var readCard027Back = Send(readCard027);
Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(readCard027Back));
}
///
/// 模拟输入函数与输出xml对应集合,以作mock
///
static Dictionary BackDic = new Dictionary {
{"ReadCard033" ,@"
病历编号
HIS端结算流水
1
2
3
4
1
操作人员编号
操作人员姓名
支付方式
单据编号
单据类别
5
"},
{ "ReadCard027",$@"
业务编号
0
错误描述信息
本次交易流水
收费项目编码
收费项目名称
开立科室编码
开立科室名称
开立医生编码
开立医生姓名
2020-12-12 13:14:15
2020-12-12 13:14:15
10
有效标志
收费人员编号
执行科室编号
单据编号
收费项目编码
收费项目名称
开立科室编码
开立科室名称
开立医生编码
开立医生姓名
2020-12-12 13:14:15
2020-12-12 13:14:15
10
有效标志
收费人员编号
执行科室编号
单据编号
"}
};
///
/// 封装调用接口
///
/// 输出参数类型
/// 输入参数
///
static T Send(Request request) where T : class
{
Console.WriteLine("输入参数:");
Console.WriteLine(request.ToXML());
var backXML = BackDic[request.GetType().Name];
return request.ToResponse(typeof(T), backXML) as T;
}
}
///
/// 请求父类
///
abstract class Request
{
public override string ToString()
{
var requestSB = new StringBuilder();
var type = this.GetType();
//遍历属性,获取属性值
foreach (var pro in type.GetProperties())
{
//处理Request的子类,所有输入参数都应该继承Request
if (pro.PropertyType.IsSubclassOf(typeof(Request)))
{
requestSB.AppendLine($"<{pro.Name}>");
requestSB.AppendLine($"{pro.GetValue(this)}");
requestSB.AppendLine($"{pro.Name}>");
}
else
{
//处理DateTime类型属性
if (pro.PropertyType.IsAssignableFrom(typeof(DateTime)))
{
var value = Convert.ToDateTime(pro.GetValue(this)).ToString("yyyyMMddHHMMSS");
requestSB.AppendLine($"<{pro.Name}>{value}{pro.Name}>");
}
else
{
requestSB.AppendLine($"<{pro.Name}>{pro.GetValue(this)}{pro.Name}>");
}
}
}
return requestSB.ToString().Trim();
}
///
/// 输成xml输入参数
///
///
public string ToXML()
{
return $"\n{this}\n ";
}
///
/// 输出参数xml转成实体类
///
/// 输出参数类型
/// 输出参数xml
///
public object ToResponse(Type type, string xml)
{
xml = $@"<?xml version=""1.0"" encoding=""utf-8""?>{xml}";
var xmlDoc = new XmlDocument();
xmlDoc.LoadXml(xml);
var instance = Activator.CreateInstance(type);
foreach (var pro in type.GetProperties())
{
//自定义实体类属性,利用在同一个命名空间里来解瘊这个事情
if (pro.PropertyType.Namespace == this.GetType().Namespace)
{
var xmlElement = xmlDoc.GetElementsByTagName(pro.Name);
if (xmlElement.Count > 0)
{
var response = ToResponse(pro.PropertyType, $"<{pro.Name}>{xmlElement[0].InnerXml}{pro.Name}>");
pro.SetValue(instance, response);
}
}
else
{
//泛型集合实体类属性
if (pro.PropertyType.IsGenericType)
{
//获取泛型集合属性的类型
var subType = pro.PropertyType.GetGenericArguments()[0];
var xmlElement = xmlDoc.GetElementsByTagName(pro.Name);
if (xmlElement.Count > 0)
{
//生成泛型集合属性的实体类
var list = Activator.CreateInstance(pro.PropertyType) as IList;
//把输出字符串中的列表对应数据添加到泛型属性集合中
foreach (XmlNode childItem in xmlElement[0].ChildNodes)
{
if (childItem.ChildNodes.Count > 0)
{
var subInstance = ToResponse(subType, $"<{subType.Name}>{xmlElement[0].ChildNodes[0].InnerXml}{subType.Name}>");
list.Add(subInstance);
}
}
//设置泛型集合属性的值
pro.SetValue(instance, list);
}
}
else
{
//普通属性
var xmlElement = xmlDoc.GetElementsByTagName(pro.Name);
if (xmlElement.Count > 0)
{
var value = Convert.ChangeType(xmlElement[0].InnerText, pro.PropertyType);
pro.SetValue(instance, value);
}
}
}
}
return instance;
}
}
///
/// 医院就诊卡接口规范(业务:033)
///
class ReadCard033 : Request
{
///
/// 业务编号
///
public string TradeCode { get; set; }
///
/// 开始日期
///
public DateTime BeginDate { get; set; }
///
/// 结束日期
///
public string EndDate { get; set; }
}
///
/// 医院就诊卡接口规范(业务:033)输出参数
///
class ReadCard033Back
{
///
/// 病历编号
///
public string PatientId { get; set; }
///
/// HIS端结算流水
///
public string SiHisOrderNo { get; set; }
///
/// 医保统筹支付(元)
///
public decimal PubCost { get; set; }
///
/// 医保帐户支付(元
///
public decimal PayCost { get; set; }
///
/// 患者个人自付(元)
///
public decimal OwnCost { get; set; }
///
/// 本次结算总额(元)
///
public decimal TotCost { get; set; }
///
/// 结算类别(1消费,0退费)
///
public int TransType { get; set; }
///
/// 操作人员编号
///
public string OperCode { get; set; }
///
/// 操作人员姓名
///
public string OperName { get; set; }
///
/// 支付方式
///
public string PayType { get; set; }
///
/// 单据信息
///
public Invoices Invoices { get; set; }
}
///
/// 单据
///
class Invoices
{
///
/// 单据编号
///
public string INum { get; set; }
///
/// 单据类别
///
public string InvoiceType { get; set; }
///
/// 单据金额(元)
///
public decimal ISum { get; set; }
}
///
/// 医院就诊卡接口规范(业务:027)
///
class ReadCard027 : Request
{
///
/// 交易码(见上表交易代码)
///
public string TradeCode { get; set; }
///
/// 交易日期(YYYYMMDD
///
public string Date { get; set; }
///
/// 交易时间(HHMMSS)
///
public string Time { get; set; }
///
/// 发票号
///
public string InvoiceNo { get; set; }
///
/// 交易类别
///
public string TransType { get; set; }
}
///
/// 医院就诊卡接口规范(业务:027)输出参数
///
class ReadCard027Back
{
///
/// 业务编号
///
public string TradeCode { get; set; }
///
/// 返回值:0 成功,其他失败
///
public string Result { get; set; }
///
/// 错误描述信息
///
public string Err { get; set; }
///
/// 本次交易流水
///
public string HospitalTransNO { get; set; }
///
/// 明细列表
///
public List Fees { get; set; }
}
///
/// 明细
///
class Fee
{
///
/// 收费项目编码
///
public string fybm { get; set; }
///
/// 收费项目名称
///
public string fymc { get; set; }
///
/// 开立科室编码
///
public string ksbm { get; set; }
///
/// 开立科室名称
///
public string ksmc { get; set; }
///
/// 开立医生编码
///
public string ysbm { get; set; }
///
/// 开立医生姓名
///
public string ysxm { get; set; }
///
/// 开单时间(yyyy-MM-dd HH:mm:ss)
///
public string kdsj { get; set; }
///
/// 收费时间(yyyy-MM-dd HH:mm:ss)
///
public string sfsj { get; set; }
///
/// 数量
///
public string fysl { get; set; }
///
/// 有效标志
///
public string yxbz { get; set; }
///
/// 收费人员编号
///
public string czy { get; set; }
///
/// 执行科室编号
///
public string zxks { get; set; }
///
/// 单据编号
///
public string InvoiceNo { get; set; }
}
}
在上面代码中,重点是Request这个抽象类,其实原ToXML中把实体类转成输入参数字符串,ToResponse是把输出字符串转成实体供程序使用。这两个方法的具体实现,在代码中有注释,这是使用反射属性来实现转换的,当然这种转换,用XmlSerializer也可以实现,但这样就失去了对属性的灵活控制,比如demo中的时间类型属性的转换。
“辛苦两个转换方法,幸福所有接口”,就是ToXML和ToResponse,如果这个医保还有几十个接口,那就只用定义每个业务函数对应的输入参数,输出参数实体类就可以了。这样简化了程序模块的耦合,层次也很清晰,但你可能在问性能了,确实,反射带来灵活的同时就会损失性能,但大部分医保接口都是以dll的形式存在,和his系统配合使用,就是一个时刻只有一个接口在调用,对性能的容忍度还是够用的。
想要更快更方便的了解相关知识,可以关注微信公众号