public override bool trygetmember(getmemberbinder binder, out object result)
{
if (!_dictionary.trygetvalue(binder.name, out result))
{
result = null;
return true;
}
var dictionary = result as idictionary<string, object>;
if (dictionary != null)
{
result = new dynamicjsonobject(dictionary);
return true;
}
var arraylist = result as arraylist;
if (arraylist != null && arraylist.count > 0)
{
if (arraylist[0] is idictionary<string, object>)
result = new list<object>(arraylist.cast<idictionary<string, object>>().select(x => new dynamicjsonobject(x)));
else
result = new list<object>(arraylist.cast<object>());
}
return true;
}
}
#endregion
}
接下来是getcontent方法,此方法的目的很简单,就是要根据客户传递的模板变量参数键值对和短信模板内容,拼装成最后的短信发送内容,之前此方法里面是硬编码的,现在我们需要变成动态获取。
短信模板的内容示例:
【一应生活】您有一件单号为expressnumbers company,已到communityname收发室,请打开一应生活app“收发室”获取取件码进行取件。
点击下载http://a.app.qq.com/o/simple.jsp?pkgname=com.ening.life
我发现这样的模板内容有问题,模板中的变量参数是直接用的英文单词表示的,而我们的短信内容中可能有时候也会存在英文单词,那么我就给所有的变量参数加上{}。修改后如下:
【一应生活】您有一件单号为{expressnumbers} {company},已到{communityname}收发室,
请打开一应生活app“收发室”获取取件码进行取件。点击下载http://a.app.qq.com/o/simple.jsp?pkgname=com.ening.life
我们需要根据客户传递过来的对象,将短信模板中的变量参数,替换成变量参数对应的值。那么我们首先就要解析这个对象中的键值对信息。
/// 把object对象的属性反射获取到字典列表中
/// </summary>
/// <param name="data">object对象</param>
/// <returns>返回dictionary(属性名,属性值)列表</returns>
static dictionary<string, string> getproperties(object data)
{
dictionary<string, string> dict = new dictionary<string, string>();
type type = data.gettype();
string[] propertynames = type.getproperties().select(p => p.name).toarray();
foreach (var prop in propertynames)
{
object propvalue = type.getproperty(prop).getvalue(data, null);
string value = (propvalue != null) ? propvalue.tostring() : "";
if (!dict.containskey(prop))
{
dict.add(prop, value);
}
}
return dict;
}
接下来是通过正则表达式来匹配短信模板内容。
/// 多个匹配内容
/// </summary>
/// <param name="sinput">输入内容</param>
/// <param name="sregex">表达式字符串</param>
/// <param name="sgroupname">分组名, ""代表不分组</param>
static list<string> getlist(string sinput, string sregex, string sgroupname)
{
list<string> list = new list<string>();
regex re = new regex(sregex, regexoptions.ignorecase | regexoptions.ignorepatternwhitespace | regexoptions.multiline);
matchcollection mcs = re.matches(sinput);
foreach (match mc in mcs)
{
if (sgroupname != "")
{
list.add(mc.groups[sgroupname].value);
}
else
{
list.add(mc.value);
}
}
return list;
}
public static string replacetemplate(string template, object data)
{
var regex = @"\{(?<name>.*?)\}";
list<string> itemlist = getlist(template, regex, "name"); //获取模板变量对象
dictionary<string, string> dict = getproperties(data);
foreach (string item in itemlist)
{
//如果属性存在,则替换模板,并修改模板值
if (dict.containskey(item))
{
template = template.replace("{"+item+"}", dict.first(x => x.key == item).value);
}
}
return template;
}
这样就讲客户传递的对象和我们的解析代码进行了解耦,客户传递的对象不再依赖于我们的代码实现,而是依赖于我们数据表中模板内容的配置。
这几个方法我是写好了,顺便弄个单元测试来验证一下是不是我要的效果,可怜的是,这个项目中根本就没用到单元测试,没办法,我自己创建一个单元测试
[testclass]
public class matchhelpertest
{
[testmethod]
public void replacetemplate()
{
//模板文本
var template = "【一应生活】您有一件单号为{expressnumbers} {company},已到{communityname}收发室,
请打开一应生活app“收发室”获取取件码进行取件。点击下载http://a.app.qq.com/o/simple.jsp?pkgname=com.ening.life";
//数据对象
var data = new { expressnumbers = "2016", company = "长城", communityname = "长怡花园"};
string str = "【一应生活】您有一件单号为2016 长城,已到长怡花园收发室,
请打开一应生活app“收发室”获取取件码进行取件。点击下载http://a.app.qq.com/o/simple.jsp?pkgname=com.ening.life";
string str1=matchhelper.replacetemplate(template, data);
assert.areequal(str1,str);
//重复标签的测试
template = "【一应生活】您有一件单号为{expressnumbers} {company},已到{communityname}收发室,单号:{expressnumbers}";
str = "【一应生活】您有一件单号为2016 长城,已到长怡花园收发室,单号:2016";
str1=matchhelper.replacetemplate(template, data);
assert.areequal(str1, str);
}
}
说到单元测试,我相信在许多公司都没有用起来,理由太多。我也觉得如果业务简单的话,根本没必要写单元测试,国内太多创业型公司项目进度都非常赶,如果说写单元测试不费时间,那绝对是骗人的,至于说写单元测试能提高开发效率,减少返工率,个人感觉这个还真难说,因为即便不写单元测试也还是可以通过许多其它手段来弥补的,个人观点,勿喷。
接下来修改getcontent方法如下:
public string getcontent(dynamic messagecontext)
{
string strmsg = "";
string typecode = string.isnullorempty(messagecontext.servicecode) ? "001" : messagecontext.servicecode;
string channel = messagecontext.channel;
try{
var module = unitofwork.messagemodule.get(c => c.type == channel && c.typeno == typecode).firstordefault();
if (!string.isnullorempty(module.content))
{
var content = module.content;
strmsg = matchhelper.replacetemplate(content, messagecontext);
}
return strmsg;
}
catch (exception ex)
{
strmsg = ex.message;
}
return strmsg;
}
(话外:先吐槽一下之前这个变量命名,messagecontext messagecontext 和string messagecontent,长得太像了,一开始我重构的时候害我弄错了,建议不要在同一个方法中使用相似的变量名称,以免弄混淆。妈蛋,老司机的我又被坑了,愤怒,无可忍受,果断重命名。)
原来控制器调用业务逻辑代码是直接这样的
messagemodulebusiness message
modulebusiness = new messagemodulebusiness()
依赖于具体类的实现,而我们知道,具体是不稳定的,抽象才是稳定的,我们应该面向接口编程。今天是发送短信,明天可能就是发邮件,又或者要加日志记录等等等。
public interface imessagemodulebusiness
{
/// <summary>
/// 组装消息内容
/// </summary>
/// <param name="messagecontext">动态参数对象</param>
/// <returns>组装后的消息内容</returns>
string getcontent(dynamic messagecontext);
}
然后调用的代码修改为:
private imessagemodulebusiness message
modulebusiness = new messagemodulebusiness();
最终的externalmerchantsendmessage代码为:
/// 外部商户发送信息
public actionresult externalmerchantsendmessage()
{
try
{
dynamic param = null;
string json = request.querystring.tostring();
if (request.querystring.count != 0) //ajax get请求
{
//兼容旧的客户调用写法,暂时硬编了
if (json.contains("param."))
{
json = json.replace("param.", "");
}
json = "{" + json.replace("=", ":'").replace("&", "',") + "'}";
}
else //ajax post请求
{
request.inputstream.position = 0;//切记这里必须设置流的起始位置为0,否则无法读取到数据
json = new streamreader(request.inputstream).readtoend();
}
var serializer = new javascriptserializer();
serializer.registerconverters(new[] { new dynamicjsonconverter() });
param = serializer.deserialize(json, typeof(object));
logger.info("[externalmerchantsendmessage]param:" + param);
bool isauth = authmodelbusiness.isauth(param.channel, param.phone, param.sign);
if (!isauth)
{
return json(new result<string>()
{
resultcode = ((int)resultcode.nopermission).tostring(),
resultmsg = "签名或无权限访问"
}, jsonrequestbehavior.allowget);
}
var meaage = messagemodulebusiness.getcontent(param);
if (string.isnullorempty(meaage))
{
return json(new result<string>()
{
resultcode = ((int)resultcode.failure).tostring(),
resultmsg = "发送失败"
}, jsonrequestbehavior.allowget);
}
smshelper helper = new smshelper();
helper.sendsms(meaage, param.phone); //发送短信
return json(new result<string>()
{
resultcode = ((int)resultcode.success).tostring(),
resultmsg = "发送成功"
}, jsonrequestbehavior.allowget);
}
catch (exception ex)
{
return json(new result<string>()
{
resultcode = ((int)resultcode.failure).tostring(),
resultmsg = "发送失败"+ex.message
}, jsonrequestbehavior.allowget);
}
}
这样的话,即便日后通过反射或者ioc来再次解耦也方便。
好了,通过这样一步一步的重构,在不修改原有表结构和不影响客户调用的情况下,我已经将变化点进行了封装,当客户的模板参数变量变化的时候,再也不需要变更代码,只需要修改表中的模板内容就可以了。
重构时,画类图是一个非常好的习惯,代码结构一目了然,这里我附上类图。
以上就是记一次.net代码重构(下)的内容。