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; }
接下來是通過Regex來匹配簡訊模板內容。
/// 多個匹配內容 /// </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 messageModuleBusiness = new MessageModuleBusiness()
依賴於具體類的實現,而我們知道,具體是不穩定的,抽象才是穩定的,我們應該面向介面編程。今天是傳送簡訊,明天可能就是發郵件,又或者要加日誌記錄等等等。
public interface IMessageModuleBusiness{ /// <summary> /// 組裝訊息內容 /// </summary> /// <param name="messageContext">動態參數對象</param> /// <returns>組裝後的訊息內容</returns> string GetContent(dynamic messageContext);}
然後調用的代碼修改為:
private IMessageModuleBusiness messageModuleBusiness = 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代碼重構(下)的內容,更多相關內容請關注topic.alibabacloud.com(www.php.cn)!