记一次.NET代码重构(上)

需求:是这样的,要开发一个短信发送的模板,不同客户可能会使用不同的模板,而不同的客户使用的变量参数也是不同的。

之前为了应急,线上已经完成了一个短信模板发送短信的功能,短信模板表也创建了,而且在表中已经新增了一条记录。我只需要做一个短信模板的增删改查界面就可以了,看上去我的任务挺简单的,老司机应该知道,接了个烂摊子。

下图所示是原来已经创建好了的表

897.jpg

SQL创建脚本如下:

898.jpg

在这之前是已经开发了一个发送短信的API接口供客户调用了的,也就是说调用方(客户),不会修改代码,只能我这边来修改。虽然极不情愿接做了一半的任务,但是没办法,不可能给你的开发任务都是从头开始的。

实体类代码如下:

899.jpg

 DOT类:

900.jpg

这是之前的代码,业务实体类MessageModuleBusiness.cs代码如下:

public class MessageModuleBusiness : GenericRepository    {        private UnitOfWork.UnitOfWork unitOfWork = new UnitOfWork.UnitOfWork();        /// 获取模版内容        public string GetContent(MessageContext messageContext)        {            string messageContent = "";            string TypeCode = string.IsNullOrEmpty(messageContext.serviceCode) ? "001" : messageContext.serviceCode;            try            {                var Module = unitOfWork.MessageModule.Get(c => c.Type == messageContext.channel && c.TypeNo == TypeCode).FirstOrDefault();                //Content的内容:【一应生活】您有一件单号为expressNumbers company,                已到communityName收发室,请打开一应生活APP“收发室”获取取件码进行取件。点击下载http://a.app.qq.com/o/simple.jsp?pkgname=com.ening.life                if (!string.IsNullOrEmpty(Module.Content))                {                    var content = Module.Content;                    content = content.Replace("company", messageContext.company);                    content = content.Replace("expressNumbers", messageContext.expressNumbers);                    content = content.Replace("communityName", messageContext.communityName);                    content = content.Replace("Id", messageContext.Id);                    content = content.Replace("receiveTime", messageContext.receiveTime);                    content = content.Replace("fetchCode", messageContext.fetchCode);                    messageContent = content;                }                return messageContent;            }            catch (Exception ex) {}            return "";        }         #endregion}

MessageContext类,这个是客户端传输过来调用的一个实体对象。对象里面存在许多类似于短信的动态标签变量。

public class MessageContext{        /// 手机号码        public string phone { get; set; }        /// 发送信息        public string message { get; set; }        /// 签名        public string sign { get; set; }        /// 渠道        public string channel { get; set; }        /// 内容        public string content { get; set; }        /// 取件码        public string fetchCode { get; set; }        /// 快递公司        public string company { get; set; }        /// 快递单号        public string expressNumbers { get; set; }        /// 社区名称        public string communityName { get; set; }        /// 到件时间        public string receiveTime { get; set; }        /// 序号        public string Id { get; set; }        /// 业务代码        public string serviceCode { get; set; }    }

控制器方法externalMerchantSendMessage,这是供外部调用的

    /// 外部商户发送信息        public ActionResult externalMerchantSendMessage(MessageContext param)        {            logger.Info("[externalMerchantSendMessage]param:" + param);            bool isAuth = authModelBusiness.isAuth(param.channel, param.phone, param.sign);            if (!isAuth)            {                return Json(new Result()                {                    resultCode = ((int)ResultCode.NoPermission).ToString(),                    resultMsg = "签名或无权限访问"                }, JsonRequestBehavior.AllowGet);            }            var meaage = messageModuleBusiness.GetContent(param);            if (string.IsNullOrEmpty(meaage))            {                return Json(new Result()                {                    resultCode = ((int)ResultCode.failure).ToString(),                    resultMsg = "发送失败"                }, JsonRequestBehavior.AllowGet);            }            SMSHelper helper = new SMSHelper();            helper.SendSMS(meaage, param.phone);            return Json(new Result()            {                resultCode = ((int)ResultCode.success).ToString(),                resultMsg = "发送成功"            }, JsonRequestBehavior.AllowGet);        }

以上是我接收开发任务之前已经实现了的功能。看上去我的任务挺简单的,可是多年的开发经验告诉我,这里需要重构,如果我现在啥都不管,就只管做一个短信模板的增删改查界面的话,后面维护的人一定会抓狂。

看出什么问题没有?

这个接口方法externalMerchantSendMessage是给所有客户调用,而不同客户使用不同的短信模板,不同的模板,又存在不同的变量参数。而现在所有的变量参数都封装在了类MessageContext中,问题是我们无法一下子把所有的变量参数全部确定下来,并保持不变。

那么,也就是说一旦需要添加变量参数,类MessageContext中的代码就必须修改,而且GetContent方法中的代码是硬编的,一样需要跟着修改。这样就形成了一个循环,不断加变量参数,不断改代码,不断发布接口版本…….

时间充裕的情况下,我自然是一个有节操的程序猿,那么就开始重构吧。

在重构之前,在脑海浮现的并不是各种设计模式,而是面向对象设计的基本原则。各种设计模式就好比各种武学套路或者招式,习武之人应该像张无忌练习太极剑一样,先学会各种套路,然后忘记所有套路,从而融会贯通。

因为招式是死的,人是活得,有招就有破绽,根本没有必胜招式存在,就好像没有万能的设计模式一样,任何设计模式都存在缺点。

面向对象设计的核心思想就是封装变化,那么先找出变化点。从上面的分析中,我们已经发现了变化点,那就是短信模板中的变量参数,而这些变量参数都是客户调用方传过来的,不同客户传递的参数变量又可能是不一样的。

我们先来看一下,客户传递过来的是什么?我们看下客户调用代码,这里有Get和Post两种调用方式。

function sendMsg() {            //var appParam ="phone=15914070649&sign=78a7ce797cf757916c2c7675b6865b54&channel=weijiakeji&content=&fetchCode=1&company=%E9%A1%BA%E4%B8%B0%E5%BF%AB%E9%80%92&expressNumbers=123456&communityName=%E9%95%BF%E5%9F%8E%E4%B8%80%E8%8A%B1%E5%9B%AD&receiveTime=5&Id=1231";            //Get("/Message/externalMerchantSendMessage?" + appParam, {});            var data = {                "phone": "15914070649", "sign": "78a7ce797cf757916c2c7675b6865b54", "channel": "weijiakeji",                "fetchCode": 1, "company": "%E9%A1%BA%E4%B8%B0%E5%BF%AB%E9%80%92", "Id": "1231"            };            Post('/Message/externalMerchantSendMessage', data);        }        //WebAPI Post方法        function Post(url, data) {            $.ajax({                url: url,                contentType: "application/json",                type: "POST",                dataType: "json",                async: true,                cache: false,                data: JSON.stringify(data),                success: function (response) {                    $('#response').text(JSON.stringify(response));                },                error: function (XMLHttpRequest, textStatus, errorThrown) {                    alert(textStatus);                }            });        };        //// WebApi Get方法        function Get(url, data) {            $.ajax({                url: url,                contentType: "application/json",                type: "GET",                dataType: "json",                async: true,                cache: false,                //data: JSON.stringify(data),                success: function (response) {                    $('#response').text(JSON.stringify(response));                },                error: function (XMLHttpRequest, textStatus, errorThrown) {                    alert(textStatus);                }            });        };

可见客户传递的是一个键值对集合,就是一个JSON格式的对象。根据前面的代码 bool isAuth = authModelBusiness.isAuth(param.channel, param.phone, param.sign);,可以分析出有三个参数是所有调用客户都必须传递过来的,那就是:channel,phone,sign,而其它的参数就是短信模板的变量参数和参数值。

那么方法externalMerchantSendMessage(MessageContext param)中的参数就是一个可变对象。在C#4.0种存在一个dynamic不正是用来描述可变对象吗?

那么第一步修改传入参数类型,之前是硬编码的强类型MessageContext,现在不依赖此类,而是动态解析,修改externalMerchantSendMessage方法代码如

下:

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));

DynamicJsonConverter的作用是将JSON字符串转为Object对象,代码如下:

using System;using System.Collections;using System.Collections.Generic;using System.Collections.ObjectModel;using System.Dynamic;using System.Linq;using System.Text;using System.Web.Script.Serialization;public sealed class DynamicJsonConverter : JavaScriptConverter{    public override object Deserialize(IDictionary dictionary, Type type, JavaScriptSerializer serializer)    {        if (dictionary == null)            throw new ArgumentNullException("dictionary");        return type == typeof(object) ? new DynamicJsonObject(dictionary) : null;    }    public override IDictionary Serialize(object obj, JavaScriptSerializer serializer)    {        throw new NotImplementedException();    }    public override IEnumerable SupportedTypes    {        get { return new ReadOnlyCollection(new List(new[] { typeof(object) })); }    }  #region Nested type: DynamicJsonObject    private sealed class DynamicJsonObject : DynamicObject    {        private readonly IDictionary _dictionary;        public DynamicJsonObject(IDictionary dictionary)        {            if (dictionary == null)                throw new ArgumentNullException("dictionary");            _dictionary = dictionary;        }        public override string ToString()        {            var sb = new StringBuilder("{");            ToString(sb);            return sb.ToString();        }        private void ToString(StringBuilder sb)        {            var firstInDictionary = true;            foreach (var pair in _dictionary)            {                if (!firstInDictionary)                    sb.Append(",");                firstInDictionary = false;                var value = pair.Value;                var name = pair.Key;                if (value is string)                {                    sb.AppendFormat("{0}:"{1}"", name, value);                }                else if (value is IDictionary)                {                    new DynamicJsonObject((IDictionary)value).ToString(sb);                }                else if (value is ArrayList)                {                    sb.Append(name + ":[");                    var firstInArray = true;                    foreach (var arrayValue in (ArrayList)value)                    {                        if (!firstInArray)                            sb.Append(",");                        firstInArray = false;                        if (arrayValue is IDictionary)                            new DynamicJsonObject((IDictionary)arrayValue).ToString(sb);                        else if (arrayValue is string)                            sb.AppendFormat(""{0}"", arrayValue);                        else                            sb.AppendFormat("{0}", arrayValue);                    }                    sb.Append("]");                }                else                {                    sb.AppendFormat("{0}:{1}", name, value);                }            }            sb.Append("}");        }

以上就是记一次.NET代码重构(上)的内容,更多相关内容请关注PHP中文网(www.php.cn)!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1432066.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年12月17日 06:10:35
下一篇 2025年12月14日 20:05:13

相关推荐

  • 记一次.NET代码重构(下)

    public override bool TryGetMember(GetMemberBinder binder, out object result){ if (!_dictionary.TryGetValue(binder.Name, out result)) { result = null; …

    2025年12月17日
    000
  • 为 Jenkins 配置 .NET 持续集成环境

    去年年底,得益于公司引入 jenkins,让我们在持续集成方面迈出了第一步,本文不赘述如何安装 jenkins,主要关注点在于配置 .net 环境。另外本文是在 windows 环境下安装的 jenkins 进行操作。 一、安装环境 首先我们需要先准备几个安装包,将它们安装到 Windows 上: …

    2025年12月17日
    000
  • c#.net中const和readonly的区别

    (1) readonly和const都是用来标示常量的。(2) 初始化赋值不同。const修饰的常量必须在声明的同时赋值。例如: public class Class1 { public const int MaxValue = 10; //正确声明 public const MInValue; /…

    好文分享 2025年12月17日
    000
  • XML中如何生成XML报表模板_XML生成XML报表模板的方法与示例

    利用XSLT、编程语言或模板引擎可生成XML报表模板:1. XSLT将源XML转换为结构化报表;2. Python等语言通过DOM操作动态构建XML;3. Jinja2等模板引擎支持变量与逻辑控制,实现灵活输出。 在XML中生成XML报表模板,实际上是指利用XML的结构化特性设计一个可复用的数据模板…

    2025年12月17日
    000
  • XML中如何删除指定节点_XML删除指定节点的方法与技巧

    使用DOM、XPath、SAX/StAX或工具库可删除XML指定节点。DOM适合中小文件,通过removeChild()删除目标节点;XPath支持复杂条件精准定位;SAX/StAX流式处理适用于大文件;工具库如ElementTree提供简洁API。选择方法需考虑文件大小与性能需求。 在处理XML文…

    2025年12月17日
    000
  • XML中如何校验XML节点顺序_XML校验XML节点顺序的方法与技巧

    答案:使用XSD的xs:sequence可严格校验XML节点顺序,如FirstName→LastName→Age;若顺序错乱则校验失败。 在XML处理过程中,校验节点顺序是确保数据结构符合预期的重要环节。特别是在与外部系统交互、接口对接或数据导入导出时,严格的节点顺序可能影响解析结果或业务逻辑。虽然…

    2025年12月17日
    000
  • XML中如何处理空白节点_XML处理空白节点的操作步骤

    正确处理XML空白节点需根据解析器设置或编程逻辑过滤非重要空白。例如Java DOM可设setIgnoringElementContentWhitespace(true),Python可预处理移除,.NET可通过PreserveWhitespace=false控制,默认保留空白;也可通过DTD/XS…

    2025年12月17日
    000
  • XML中如何合并XML片段_XML合并XML片段的操作方法与技巧

    正确合并XML片段需先创建统一根节点,再通过编程语言的XML库或XSLT将各片段导入,确保编码、命名空间和属性唯一性,避免字符串拼接以防止结构错误。 在处理XML数据时,经常需要将多个XML片段合并成一个完整的文档。这种操作常见于配置文件整合、数据聚合或服务间通信场景。正确地合并XML片段不仅能保证…

    2025年12月17日
    000
  • XML中如何生成带CDATA节点的XML_XML生成带CDATA节点XML的方法与示例

    使用lxml、Java DOM和C# XmlDocument可生成带CDATA的XML,分别通过etree.CDATA、createCDATASection和CreateCDataSection方法实现,注意避免嵌套及编码问题。 在XML中,CDATA(Character Data)节点用于包裹文本…

    2025年12月17日
    000
  • XML中如何解析嵌套列表属性_XML解析嵌套列表属性的方法与步骤

    解析XML嵌套列表属性需结合DOM遍历、XPath查询与数据封装。首先使用ElementTree或lxml加载XML,通过findall或XPath定位item节点,提取id、type等属性及name、quantity等子元素文本,逐层解析后将结果存为字典列表,便于后续操作。 在处理XML数据时,经…

    2025年12月17日
    000
  • XML中如何解析带DTD的XML_XML解析带DTD的XML的操作方法

    解析带DTD的XML需根据需求启用或关闭验证:Java中通过setValidating(true)开启,Python和.NET需配置支持DTD的解析器;处理外部DTD时应确保路径可访问或使用EntityResolver映射本地文件,注意安全风险;若仅解析结构可关闭验证以提升性能。 解析带有 DTD(…

    2025年12月17日
    000
  • 什么是XML Encryption

    XML Encryption通过加密XML数据保障机密性,支持细粒度加密,利用CEK和KEK双重加密机制,结合和结构实现安全封装,并常与XML Signature协同使用以同时确保机密性、完整性和认证。 XML Encryption 是一种由万维网联盟(W3C)定义的技术标准,它允许我们对整个 XM…

    2025年12月17日
    000
  • XML中如何转化为对象_XML将XML转化为对象的方法与技巧

    使用JAXB将XML转Java对象需添加@XmlRootElement和@XmlElement注解,通过JAXBContext和Unmarshaller解析;2. C#中用XmlSerializer反序列化,类标记[XmlRoot]或[Serializable],调用Deserialize方法读取流…

    2025年12月17日
    000
  • XML注入攻击是什么?如何防范?

    XML注入发生在用户输入被直接拼接进XML文档且未转义特殊字符时,例如输入true可篡改权限结构。防范措施包括:对&等字符进行转义为&;使用DOM、XmlWriter等安全库生成XML避免手动拼接;严格验证输入格式与长度;禁用DTD和外部实体防止XXE攻击;在开发中始终净化所有不可信…

    2025年12月17日
    000
  • XML中如何处理空白节点_XML处理空白节点的技巧与步骤

    正确处理XML空白节点需根据场景选择策略:解析时可通过设置忽略空白、使用XPath精准定位或预处理清洗文本,避免格式化空白影响数据准确性。 在处理XML文档时,空白节点(如换行、空格、制表符等)常常会影响数据解析的准确性。尤其在使用DOM或XPath解析时,这些看似无害的空白可能会被识别为文本节点,…

    2025年12月17日
    000
  • XML配置文件如何设计?常见应用场景?

    设计XML配置文件需遵循语义化命名、合理使用属性与子元素、支持注释和Schema验证等原则,适用于Spring框架、Tomcat配置、Maven构建、Logback日志等场景,强调结构清晰、可扩展性和可维护性,尤其在企业级应用中仍具优势。 XML配置文件的设计核心在于结构清晰、可读性强、易于扩展。它…

    2025年12月17日
    000
  • XML与配置文件热重载如何实现?监听文件变化。

    实现XML配置热重载需监听文件变化、重新解析并安全替换配置。首先利用WatchService等工具监听文件修改事件;检测到变更后,异步重新解析XML,校验语法并对比新旧配置;通过原子引用或双缓冲机制更新内存配置,避免阻塞主线程和频繁抖动;最后通知相关组件同步状态。结合Spring Boot或配置中心…

    2025年12月17日
    000
  • 如何设计XML的国际化方案

    答案:设计XML国际化方案需分离可翻译内容与结构,推荐外部化资源文件并使用UTF-8编码、清晰翻译键、本地化格式处理及自动化工具链,以应对字符编码、上下文丢失、多语言同步等挑战,确保可维护性与扩展性。 设计XML的国际化方案,核心在于将可翻译内容与结构、逻辑分离,并为不同语言提供明确的标识或独立的存…

    2025年12月17日
    000
  • XML中如何处理非法字符_XML处理非法字符的技巧与方法

    XML解析失败常因非法字符导致,需清理控制字符并保留合法范围#x9、#xA、#xD及#x20-#xD7FF、#xE000-#xFFFD,可通过正则预处理或CDATA包裹已清洗内容,结合XML库容错机制有效避免异常。 在处理XML数据时,经常会遇到非法字符导致解析失败的问题。XML对可接受的字符有严格…

    2025年12月17日
    000
  • 什么是XML Swiss Army Knife

    “XML瑞士军刀”指的是一套多功能、集成化的工具集,用于应对XML数据处理的多样性与复杂性。它涵盖解析(DOM/SAX/StAX)、验证(DTD/XSD)、查询(XPath/XQuery)、转换(XSLT)及编辑工具(如Oxygen XML Editor),需根据项目需求、技术栈和成本灵活组合使用,…

    2025年12月17日
    000

发表回复

登录后才能评论
关注微信