近期开发项目中用到的编码小技巧汇总说明(二)
之前有总结发表过《近期开发项目中用到的编码小技巧汇总说明》,虽没有涉及什么高大上的东西,但都是一些很实用的平时大家可能用到的知识,今天继续分享一些小技巧,欢迎大家了解,不足之处,可以直接评论留言谢谢!
接上篇序号
6.解决当同一个类在不同的项目中(命名空间不同,但类的定义完全相同的情况)使用BinaryFormatter进行序列化后再反序列化时出现找不到程序集的问题或反序列化的结果为null
原代码:(DataSetSurrogate分别在API项目中,客户端项目中都存在,类定义一样但由于不在同一个项目,即使命名空间改成一样仍然是会报错的)
public static DataSet GZipBytesToDataSet(byte[] data) { byte[] buffer2 = data; BinaryFormatter ser = new BinaryFormatter(); var ms = new MemoryStream(buffer2); var obj = ser.Deserialize(ms); DataSetSurrogate dss = obj as DataSetSurrogate; return dss.ConvertToDataSet(); }
报错原因是:序列化后的byte数组中包含了程序集的信息,故如果想要在另一个程序集中成功的反序列化,则需要动态替换反序列化中的类型对应的程序集信息,改进后的代码:
先定义一个类型名称替换序列化绑定器:
public class TypeNameConvertBinder : SerializationBinder { public TypeNameConvertBinder():base() { } public TypeNameConvertBinder(string oldNameSapce, string newNameSapce):this() { this.OldNameSapce = oldNameSapce; this.NewNameSapce = newNameSapce; } public string OldNameSapce { get; set; } public string NewNameSapce { get; set; } public override Type BindToType(string assemblyName, string typeName) { typeName = typeName.Replace(OldNameSapce, NewNameSapce); assemblyName = assemblyName.Replace(OldNameSapce, NewNameSapce); return Type.GetType(string.Format("{0}, {1}", typeName, assemblyName)); } }
然后序列化的时候直接给 BinaryFormatter设置Binder即可,代码如下:
public static DataSet GZipBytesToDataSet(byte[] data) { byte[] buffer2 = data; BinaryFormatter ser = new BinaryFormatter(); ser.Binder = new TypeNameConvertBinder() { OldNameSapce = "WMS.Common", NewNameSapce = "Zuowj" }; var ms = new MemoryStream(buffer2); var obj = ser.Deserialize(ms); DataSetSurrogate dss = obj as DataSetSurrogate; return dss.ConvertToDataSet(); }
以上这样就解决了序列化时因为程序集不相同而导致的反序列化失败的问题。
7.控制台程序中实现输入密码遮罩功能,这个功能我是摘抄自网上的,还可以有优化空间
实现代码如下:
static string ReadLineForPassword() { string input = null; while (true) { //存储用户输入的按键,并且在输入的位置不显示字符 ConsoleKeyInfo ck = Console.ReadKey(true); //判断用户是否按下的Enter键 if (ck.Key != ConsoleKey.Enter) { if (ck.Key != ConsoleKey.Backspace) { //将用户输入的字符存入字符串中 input += ck.KeyChar.ToString(); //将用户输入的字符替换为* Console.Write("*"); } else { if (!string.IsNullOrEmpty(input) && input.Length >= 1) { input = input.Remove(input.Length - 1, 1); } //删除错误的字符 Console.Write("\b \b"); } } else { Console.WriteLine(); break; } } return input; } //使用: string token = ReadLineForPassword();
这样当用户在控制台上输入密码时则会以*号隐藏掉了。
8.直接使用String拼接XML的时候,有时可能因为字符串中包含了XML的保留字符,那么拼成后就会报错,故需要转义这些保留字,如果自己写可能考虑得不全,这时可以使用:SecurityElement.Escape(String) 方法进行转义即可。
原来的代码:
var st =new System.Diagnostics.StackTrace(); string msg = "<key>无效,使用'key'也无效 "; string xml = "<?xml version=\"1.0\" encoding=\"utf-8\" ?><root><Msg>" + msg + "</Msg><StackTrace>" + st.ToString() + "</StackTrace></root>"; XmlDocument xmlDoc = new XmlDocument(); xmlDoc.LoadXml(xml); StringBuilder strBuilder=new StringBuilder(); using (var xmlWriter = XmlWriter.Create(strBuilder)) { xmlDoc.WriteTo(xmlWriter); } Console.WriteLine("new xml:" + strBuilder.ToString()); Console.ReadKey();
执行到xmlDoc.LoadXml(xml);报错,因为msg中包含了XML的保留字符,改进一下 msg = System.Security.SecurityElement.Escape("<key>无效,使用'key'也无效 ");然后再用同样的代码即可正常显示完整的XML,结果如下:
可以看到XML的保留字符都被转义了。
9.关于WINDOWS服务安装或卸载批处理脚本bat在WIN10下执行报错问题解决办法(2019-5-28日 增加)
目前网上转载的安装与卸载的Windows服务的bat内容如下:(注意需放到服务程序exe相同的目录中)
// install.bat内容: %SystemRoot%\Microsoft.NET\Framework\v4.0.30319\installutil.exe 服务程序exe Net Start 服务名 sc config 服务名 start= auto pause // uninstall.bat内容: net stop 服务名 %SystemRoot%\Microsoft.NET\Framework\v4.0.30319\installutil.exe /u 服务程序exe pause
在WIN 7或WIN SERVER中直接双击运行bat文件就可轻松完成服务的安装启动或服务停止卸载,但如果是在WIN10 下 直接双击运行bat文件则会报:
在“安装”阶段发生异常。
System.Security.SecurityException: 未找到源,但未能搜索某些或全部事件日志。 不可访问的日志: Security。
通过这个错误信息我们应该知道是权限不足导致的,故换成右键“以管理员身份运行”,结果仍然报如下错误:
在初始化安装时发生异常:
System.IO.FileNotFoundException: 未能加载文件或程序集“file:///C:\Windows\system32\服务名.exe”或它的某一个依 赖项。系统找不到指定的文件。。
居然报服务程序路径不存在,仔细一看,发现路径都不是当前的BAT路径,而变成了system32目录下了,这怎么回事呢?原来是以管理员身份运行惹的祸,会改变当前的执行目前环境为C:\Windows\system32,这样就导致了我们脚本中写的服务程序.exe 这样的相对路径就会在执行时变成了是C:\Windows\system32\服务名.exe,这样肯定是不行的,故解决方法有两种:
第一种:将上述脚本中的服务程序exe换成写死完整的exe路径,如:%SystemRoot%\Microsoft.NET\Framework\v4.0.30319\installutil.exe d:\test\testservice.exe
第二种:脚本中最开始行添加 动态切换cd到当前exe目录的命令(命令:cd /d %~dp0 ,含义:将扩充映射到一个驱动器和路径,简单点是切换到当前目录,命今详情可参见:https://www.cnblogs.com/feiquan/p/10368960.html),后续的脚本保持不变即可,完整代码如下:
//install.bat: cd /d %~dp0 %SystemRoot%\Microsoft.NET\Framework\v4.0.30319\installutil.exe 服务程序exe路径 Net Start 服务名 sc config 服务名 start= auto pause //uninstall.bat: cd /d %~dp0 net stop 服务名 %SystemRoot%\Microsoft.NET\Framework\v4.0.30319\installutil.exe /u 服务程序exe路径 pause
很显然第二种方法优于第一种,第一种需要写死服务程序EXE路径不便通用。