利用IFormattable接口自动参数化Sql语句
2018-06-23 21:57:33来源:未知 阅读 ()
提要
string.Format("{0},{1}",a,b)的用法大家都不陌生了,在很多项目中都会发现很多sql语句存在这样拼接的问题,这种做法很多"懒"程序员都很喜欢用,因为实在是非常的方便,但是这种做法会带来各种Sql注入的问题,所以我今天就说说这个问题,怎么才可以既方便又安全?
ps:当然这也是有代价的,代价就是性能,当然今天是忽略这个问题的,很多性能问题在小项目中都不是问题....
一号配角登场
超简版DBHelper,你可以把他理解为从某个ORM中肢解下来的一个关节
大家都是成年人了,没有技术含量的代码我就不加注释了...
public class DBHelper { public DBHelper(string connString) { ConnectionString = connString; } public string ConnectionString { get; private set; } public DataSet GetDataSet(string sql) { using (var adp = new SqlDataAdapter(sql, ConnectionString)) { var ds = new DataSet(); adp.Fill(ds); return ds; } } }
我先举个栗子
int id = 2; string name = "dsa"; DBHelper db = new DBHelper("Data Source=.;Initial Catalog=Test;Integrated Security=True"); string sql = "SELECT id,name FROM test WHERE id > {0} AND name = '{1}'"; sql = string.Format(sql, id, name); DataSet ds = db.GetDataSet(sql); Console.WriteLine(ds.Tables[0].Rows.Count);
这就是在一些项目经常看到的代码
这个代码问题刚才讲的很清楚了,因为存在Sql注入的问题.如果name参数等于 "' or 1 = 1"或者类似的语句那么会带来意想不到的灾难
你当然可以说我可以事先判断,去掉一些关键字,但你能保证已经考虑所有的情况了吗?好了,今天要讨论的不是怎么判断注入的问题,而是从根本上杜绝注入的可能!
也就是不存在字符串拼接,参数化执行Sql语句!
再来个参数化的栗子
先为DBHelper加一个方法
public DataSet GetDataSet(string sql,params SqlParameter[] args) { using (var adp = new SqlDataAdapter(sql, ConnectionString)) { adp.SelectCommand.Parameters.AddRange(args); var ds = new DataSet(); adp.Fill(ds); return ds; } }
调用就变成了这样
int id = 2; string name = "dsa"; DBHelper db = new DBHelper("Data Source=.;Initial Catalog=Test;Integrated Security=True"); //string sql = "SELECT id,name FROM test WHERE id > {0} AND name = {1}"; //sql = string.Format(sql, id, name); //DataSet ds = db.GetDataSet(sql); string sql = "SELECT id,name FROM test WHERE id > @id AND name = @name"; DataSet ds = db.GetDataSet(sql, new SqlParameter("id", id), new SqlParameter("name", name)); Console.WriteLine(ds.Tables[0].Rows.Count);
这样确实可以解决注入的问题,可是调用起来却麻烦了很多,如果参数多的时候简直就是噩梦啊~~
YY ... 你们懂的
先抛开一些杂念,想想自己想要的什么...
其实很简单,我不想每个参数都new SqlParamete()
int id = 2; string name = "dsa"; DBHelper db = new DBHelper("Data Source=.;Initial Catalog=Test;Integrated Security=True"); string sql = "SELECT id,name FROM test WHERE id > {0} AND name = {1}"; DataSet ds = db.GetDataSet(sql, id, name); Console.WriteLine(ds.Tables[0].Rows.Count);
乍看之下很简单嘛~~~~
public DataSet GetDataSet(string sql, params object[] args) { using (var adp = new SqlDataAdapter(sql, ConnectionString)) { for (int i = 0; i < args.Length; i++) { string name = "p_" + i; //为参数取名 格式 p_0,p_1,... adp.SelectCommand.Parameters.Add(new SqlParameter(name, args[i]));//加入参数 args[i] = "@" + name; //替换{0}为@p_0 } adp.SelectCommand.CommandText = string.Format(sql, args); var ds = new DataSet(); adp.Fill(ds); return ds; } }
嗯...确实是这样的,看下运行结果
如果是这样呢?
int id = 2; string name = "dsa"; DBHelper db = new DBHelper("Data Source=.;Initial Catalog=Test;Integrated Security=True"); string sql = "SELECT id,name FROM test WHERE id > {0} AND name Like {1}"; DataSet ds = db.GetDataSet(sql, id, name); Console.WriteLine(ds.Tables[0].Rows.Count);
里面有一个Like怎么办?所以我有一个更好的方案IFormattable
主角登场
private struct CommandFormatArgs : IFormattable { private SqlCommand _Command; private Object _Value; //得到SqlCommand和Value格式化的时候用 public CommandFormatArgs(SqlCommand command, object value) { _Command = command; _Value = value; } //在String.Format时会调用这个方法 public string ToString(string format, IFormatProvider formatProvider) { string name = "p_" + Identity.NextString(); _Command.Parameters.Add(new SqlParameter(name, _Value)); if (format != null && format.Contains("@")) { return "'" + format.Replace("@", "' + @" + name + " + '") + "'"; } else { return "@" + name; } } }
2号配角:Identity自增序列/唯一断标识
这个对象我设计成一个内部结构,因为他的生存周期非常的短暂,只会在方法内使用,所以结构已经够用了
重新实现GetDataSet
public DataSet GetDataSet(string sql, params object[] args) { using (var adp = new SqlDataAdapter(sql, ConnectionString)) { for (int i = 0; i < args.Length; i++) { args[i] = new CommandFormatArgs(adp.SelectCommand, args[i]); } adp.SelectCommand.CommandText = string.Format(sql, args); var ds = new DataSet(); adp.Fill(ds); return ds; } }
实现自定义格式化参数
在String.Format这个方法中,系统会调用我们实现IFormattable接口中的方法ToString,并且,如果有额外的参数也会在format参数中体现出来
额外的参数就是指 string.Format("{0:yyyy-MM-dd}",obj)中的yyyy-MM-dd
所以如果是Like,我将他指定了一个规则,如:
int id = 2; string name = "a"; DBHelper db = new DBHelper("Data Source=.;Initial Catalog=Test;Integrated Security=True"); string sql = "SELECT id,name FROM test WHERE id > {0} AND name Like {1:%@%}"; DataSet ds = db.GetDataSet(sql, id, name); Console.WriteLine(ds.Tables[0].Rows.Count);
这个调用的时候%@%会被当作format参数传到ToString(string format, IFormatProvider formatProvider)中
public string ToString(string format, IFormatProvider formatProvider) { string name = "p_" + Identity.NextString(); _Command.Parameters.Add(new SqlParameter(name, _Value)); if (format != null && format.Contains("@")) { return "'" + format.Replace("@", "' + @" + name + " + '") + "'"; } else { return "@" + name; } }
处理完的效果就是这样的
================================================
结束
我这里的结束只是指这篇文章的到这里就结束了
IFormattable的用法当然不仅限于此
写这个也仅仅只是做一个抛砖引玉的作用,其实系统有很多很多很好的接口和为这些接口服务的类和方法
只要我们运用得当,都会为我们带来非常多编码上的好处和编码以外的乐趣
标签:
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有
上一篇:c#中的表达式
- 分享nodejs分页类代码 2019-12-02
- javascript设计模式 接口介绍 2019-09-23
- vue中使用proxy配置不同端口和ip接口 2019-08-14
- page分页问题,根据页码获取对应页面的数据,接口调用 2019-08-14
- EggJS接口开发 2019-08-14
IDC资讯: 主机资讯 注册资讯 托管资讯 vps资讯 网站建设
网站运营: 建站经验 策划盈利 搜索优化 网站推广 免费资源
网络编程: Asp.Net编程 Asp编程 Php编程 Xml编程 Access Mssql Mysql 其它
服务器技术: Web服务器 Ftp服务器 Mail服务器 Dns服务器 安全防护
软件技巧: 其它软件 Word Excel Powerpoint Ghost Vista QQ空间 QQ FlashGet 迅雷
网页制作: FrontPages Dreamweaver Javascript css photoshop fireworks Flash