[转载]Jumony Core 3,真正的HTML引擎,正式版发布 - Ivony... - 博客园

mikel阅读(1518)

转载Jumony Core 3,真正的HTML引擎,正式版发布 – Ivony… – 博客园.

Jumony是一个开源项目,已经有三年的历史了,在这三年中,秉承提供给.NET程序员完整的HTML掌控能力,Jumony历经无数次的改进,终于进入了一个新的阶段。Jumony Core 3是一个真正意义上的HTML引擎。

Jumony Core 3目前已经在NuGet上发布,请直接在NuGet包管理器中搜索 Jumony Core ,即可下载。

项目地址:https://github.com/Ivony/Jumony

一、解析器

也许很多人会认为,目前的HTML解析器已经足够了,甚至于简单的正则,也已经可以满足操纵HTML文档的需求。是的,对于互联网上绝大多数的 HTML文档,事实上都大部分满足了XHTML的规范,对于它们的解析,并不需要多么强大的解析器。但是强大的解析器是一回事,而完美的解析器又是另一回 事。

Jumony Core首先提供了一个近乎完美的HTML解析引擎,其解析结果无限逼近浏览器的解析结果。不论是无结束标签的元素,可选结束标签的元素,或是标记属性, 或是CSS选择器和样式,一切合法的,不合法的HTML文档,浏览器解析成啥样,Jumony就解析成啥样。也就是说,Jumony解析的结果,与浏览器 解析的结果别无二致,让你可以再也不用关心HTML文档是否可以被识别,浏览器能看,Jumony就能解。

完美和强大只有一步之遥,但是完美的解析器可以让你永远不用关心HTML源文档。

以下是Jumony解析器所支持的特性不完全列表

特性 例子
孤立的<解析为文本 < a应当解析为&lt; a
孤立的>解析为文本 <a>></a>应当解析为<a>&gt;</a>
标记属性(没有值的属性) <input type=”text” checked />
元素丢失结束标签 <div><a href=”test.html”>测试链接</div>
可选结束标签元素
“body”, “colgroup”, “dd”, “dt”, “head”, “html”, “li”, “option”, “p”, “tbody”, “td”, “tfoot”, “th”, “thead”, “tr”
<p>abc<p>123
无结束标签元素
“area”, “base”, “basefont”, “br”, “col”, “frame”, “hr”, “img”, “input”, “isindex”, “link”, “meta”, “param”, “wbr”, “bgsound”, “spacer”, “keygen”
<img src=”1.jpg”>
CData元素 <script>if ( 1<a ) alert( “<div>” );</script>
“script”, “style”, “textarea”, “title”  
预格式化元素 <pre>    前面有空格</pre>
属性值使用单引号 <a href=’#’>
属性值使用双引号 <a href=”#”
属性值不使用引号 <a href=#>
属性值丢失(但有等号) <a href=>
属性值前面有空格 <a href= “test.html”>
解析HTML声明 <!DOCTYPE html>

 

不仅仅是可以从文本中解析HTML,Jumony的API可以从互联网上直接抓取文档分析,并根据HTTP头自动识别编码:

new JumonyParser().LoadDocument( "http://www.cnblogs.com/" ).Find( ".post_item a.titlelnk" )

而目前仅次于Jumony的HTML解析开源项目HtmlAgilityPack早已停止了更新 ,这么多年过去了,对于最基本的<form>元素的解析都还存在问题。

二、CSS样式设置支持

仅仅只是完美解析HTML,并不能带来多少好处,上面已经说过,事实上大部分的HTML文档,都可以用二流的解析器甚至是简单的正则表达式加以分析,那么为什么我们需要Jumony呢?

答案是一个HTML引擎不仅仅是解析DOM结构这么简单。

考虑这样的场景:我需要给一个元素的display样式设置一个none值。在浏览器中,我们只需要简单的 element.style.display = “none”便可以满足我们的要求。现在,通过解析器已经得到了我们所需要的DOM,但设置样式还需要进行字符串的拼接么?

不需要,Jumony支持CSS样式解析,甚至部分CSS样式缩写规则也能识别,在Jumony中,给元素设置一个样式和在浏览器中一样简单:

element.Style( "display", "none" )

 

我们再来看这样的例子:<div style=”padding: 5px”></div>,如果我们对这个元素设置padding-left: 0px会怎样?

在Jumony中,结果会是:<div style=”padding-left: 0px; padding-right: 5px; padding-top:5px; padding-bottom: 5px”></div>,看,padding属性被神奇的自动展开了。

三、CSS 3选择器支持

CSS选择器是HTML世界通行的查询语言,其简洁有力且被众多浏览器支持。Jumony也支持几乎完整的CSS3选择器(除去运行时伪类及伪对象)。借助选择器,我们可以轻松的在HTML中找到我们感兴趣的对象。例如抓取博客园首页所有文章标题:

new JumonyParser().LoadDocument( "http://www.cnblogs.com/" ).Find( ".post_item a.titlelnk" )

抓取,分析,选择,一气呵成,只需要简单的代码,我们就能在控制台输出我们抓取到的数据:

foreach( var title = new JumonyParser().LoadDocument( "http://www.cnblogs.com/" ).Find( ".post_item a.titlelnk" ) )
  Console.WriteLine( title.InnerText() ); 

 

Jumony支持的CSS3选择器列表:

选择器 描述
* 选择所有元素
p a 选择子代元素
p>a 选择子级元素
p+a 选择相邻元素
p~a 选择后继元素
[attr] 属性存在选择
[attr=value] 属性值精确匹配
[attr~=value] 属性值近似匹配
[attr^=value] 属性值开头匹配
[attr*=value] 属性值包含匹配
[attr$=value] 属性值结尾匹配
[attr!=value] 属性值否定匹配
:not 否定伪类
:only-child 唯一子元素伪类
:only-of-type 唯一类型伪类
:empty 空元素伪类
:nth-child 结构化伪类
:nth-last-child 结构化伪类
:nth-of-type 结构化伪类
:nth-last-of-type 结构化伪类
:first-child 结构化伪类
:last-child 结构化伪类
:first-of-type 结构化伪类
:last-of-type 结构化伪类

四、强大的可扩展性

在Jumony Core 3,为用户提供了最大的可扩展性,你可以自定义HTML规范,实现自己的解析器,将其他DOM模型嫁接到Jumony API上,发明自己的CSS选择器伪类,甚至于自己换一套API,例如JQuery风格的。

Jumony Core拥有许多的衍生项目,例如爬取网站,提供JQuery风格的 API、进行网站开发、制作MHT文件、为HAP的解析结果增加CSS选择器支持等等,这些项目都得益于Jumony Core强大的可扩展性,从而发挥出强大的功能。

 

Jumony也提供了一个快速上手的指南,帮助大家快速的熟悉Jumony:

http://demo.jumony.net/help?path=~%2fHelpEntries%2fQuickStart%2f

 

如你所见,这个网站也是采用Jumony进行驱动的,它会检索网站下所有的HTML页面,并抽取页面的标题以及简介,形成导航目录和概要页面。

 

为了照顾没有Visual Studio的同学,这里提供一个压缩包的下载:

http://files.cnblogs.com/Ivony/JumonyCore.3.0.5.2.zip

 

但使用Visual Studio开发的最佳实践是直接使用NuGet管理你所有的程序包。

[转载]Jumony快速抓取网页 --- Jumony使用笔记--icode - 郝喜路 - 博客园

mikel阅读(1109)

[转载]Jumony快速抓取网页 — Jumony使用笔记–icode – 郝喜路 – 博客园.

刚刚在博客园 看到一篇博文《使用HttpWebRequest和HtmlAgilityPack抓取网页(拒绝乱码,拒绝正则表达式)》  ,感觉不错,作者写的也挺好的,然后在看了园子里的朋友的评论后,我知道了有一个更牛x的工具——Jumony 。这个工具用起来可谓称之为简单、高效。 特此记录和分享,Jumony 的使用方法。

Jumony是开源项目,目前源代码存放咋GitHub ,源码地址: https://github.com/Ivony/Jumony 。我测试使用的是Visual Studio 2012 ,测试网页为博客园。

下面介绍使用方法:

一、在新建项目后,需要将Jumony添加到项目中,你可以下载源码使用,也可以在NugGet 中 搜索 Jumony Core 将其添加到项目中并且后自动添加所需的引用。

二、添加引用之后,即可写项目代码。(此处代码为获取  博客园首页文章内容)

public string Html = string.Empty;//为将拼接好html字符串返回给前台代码
protected void Page_Load(object sender, EventArgs e)
{
var htmlSource = new JumonyParser().LoadDocument("http://www.cnblogs.com").Find(".post_item a.titlelnk");
int count = 0;
foreach (var htmlElement in htmlSource)
{
count ++;
Html += string.Format("
<ul>
	<li>{2}、  <a href="\&quot;About.aspx?Url={0}\&quot;" target="\&quot;_blank\&quot;">{1}</a></li>
</ul>
&nbsp;

", htmlElement.Attribute("href").Value(), htmlElement.InnerText(),count);
}
}

  效果图:

 

三、下面就是要在点击上图从博客园抓取的文章标题之后,在显示博客全文(并非在打开博客园的文章)

string html = Request["Url"];
var htmlSource =
new JumonyParser().LoadDocument(html);
HtmlText = htmlSource.Find(".postTitle2").FirstOrDefault().InnerText();

Html = htmlSource.Find("#cnblogs_post_body").FirstOrDefault().InnerHtml();

[转载]SQLite数据库 - snow365 - 博客园

mikel阅读(1240)

[转载]SQLite数据库 – snow365 – 博客园.

SQLite是一个老牌的轻量级别的文件数据库,完全免费,使用方便,不需要安装,无须任何配置,也不需要管理员。
它是开源的嵌入式数据库产品,是同类产品中的后起之秀,2005年获得了开源大奖,而且最新的PHP5也内嵌了SQLite。相比另一款著名的嵌入式数据库——Berkely DB。SQLite是关系型数据库,支持大部分SQL语句,这是它比BDB优秀的地方。
作为一款嵌入式数据库,SQLite与Berkely DB一样,以库的形式提供,通过C函数直接操作数据库文件。(也支持其他的访问方式,比如Tcl)。下载包中有SQLite3.dll和 SQlite3.def,def可以用VC的lib工具生成链接库,当然也可以直接链接dll文件。
SQLite不是Server,所以和SQLServer等不同,它和程序运行在同一进程。中间没有进程间通信,速度很快,而且体积小巧,易于分发。适合运行在单机环境和嵌入式环境。(随便说一下,腾讯的QQ中可能就用到了SQLite数据库来保存信息)

 

支持事务机制和blob数据类型。支持大部分SQL92标准.一个完整的数据库保存在磁盘上面一个文件.
同一个数据库文件可以在不同机器上面使用,最大支持数据库到2TB.
源代码开放, 代码95%有较好的注释,简单易用的API.
现在已经发展到了SQLite3版本,目前最新版本是SQLite3.3.4.

 

资源下载:

Sqlite .net 支持 http://files.cnblogs.com/snow365/SQLite-1.0.31.0-binary.rar

哎,找到好久,终于找到了一个sqlite的图形管理工具SharpPlus Sqlite Developer 。 SQLite Spy 也不错,界面很美观,我感觉功能少了点。

 

SQLite的源码:http://www.sqlite.org/sqlite-source-3_3_4.zip(包括sqlite3.h)
SQLite编译好的dll及def文件:http://www.sqlite.org/sqlitedll-3_3_4.zip

 

SQLite提供一个命令行shell的工具用以访问数据库,下载windows下的SQLite命令行工具sqlite3.exe,
下载地址:http://www.sqlite.org/sqlite-3_3_4.zip
网上有一款针对SQLite3的 UI工具-SQLite Spy。下载地址:www.yunqa.de/delphi/sqlitespy/

 

如果想通过ODBC来访问操作SQLite数据库,需要安装第三方组件库的SQLite ODBC Driver,
可以到”http://www.patthoyts.tk/sqlite3odbc.html“或者”http://www.ch-werner.de/sqliteodbc/“去下载.
也可以直接下载其ODBC的驱动安装程序:”http://www.ch-werner.de/sqliteodbc/sqliteodbc.exe
现在SQLite ODBC Driver的版本为0.65.
然后在C++程序中就可以使用OTL来统一对数据库的访问。

 

SQLite的官方主站:http://www.sqlite.org/
SQLite的中文网:http://sqlitecn.feuvan.net/index.html
OTL的官方主站:http://otl.sourceforge.net/home.htm

其它参考:
http://www-128.ibm.com/developerworks/cn/opensource/os-sqlite/index.html?ca=dwcn-newsletter-opensource
http://blog.donews.com/limodou/archive/2004/03/21/7997.aspx

[转载]c# Sqlite帮助类 - Only Version - 博客园

mikel阅读(868)

[转载]c# Sqlite帮助类 – Only Version – 博客园.

最近有WPF做客户端,需要离线操作存储数据,在项目中考虑使用SQLite嵌入式数据库,在网上找了不少资料,最终整理出一个公共的帮助类。

SQLite是一个非常小巧的数据库,基本上具备关系型数据库操作的大多数功能,SQL语法也大同小异。下面是我整理的帮助类代码:

1.获取 SQLiteConnection 对象,传入数据库有地址即可。

          /// <summary>
         /// 获取数据库链接
         /// </summary>
         /// <returns></returns>
         public static SQLiteConnection GetSQLiteConnection()       {
             //Sqlite数据库地址
             string str = AppDomain.CurrentDomain.BaseDirectory;
             var con = new SQLiteConnection("Data Source=" + str + "DataBass\\InfoServiceDbB.db");
             return con;
         }

2.准备操作命令参数,构造SQLiteCommand 对象:

/// <summary>
        /// 准备操作命令参数
        /// </summary>
        /// <param name="cmd">SQLiteCommand</param>
        /// <param name="conn">SQLiteConnection</param>
        /// <param name="cmdText">Sql命令文本</param>
        /// <param name="data">参数数组</param>
        private static void PrepareCommand(SQLiteCommand cmd, SQLiteConnection conn, string cmdText, Dictionary<String, String> data)
        {
            if (conn.State != ConnectionState.Open)
                conn.Open();
            cmd.Parameters.Clear();
            cmd.Connection = conn;
            cmd.CommandText = cmdText;
            cmd.CommandType = CommandType.Text;
            cmd.CommandTimeout = 30;
            if (data!=null&&data.Count >= 1)
            {
                foreach (KeyValuePair<String, String> val in data)
                {
                    cmd.Parameters.AddWithValue(val.Key, val.Value);
                }
            }
        }

3.查询,返回DataSet

/// <summary>
        /// 查询,返回DataSet
        /// </summary>
        /// <param name="cmdText">Sql命令文本</param>
        /// <param name="data">参数数组</param>
        /// <returns>DataSet</returns>
        public static DataSet ExecuteDataset(string cmdText, Dictionary<string, string> data)
        {
            var ds = new DataSet();
            using (SQLiteConnection connection = GetSQLiteConnection())
            {
                var command = new SQLiteCommand();
                PrepareCommand(command, connection, cmdText, data);
                var da = new SQLiteDataAdapter(command);
                da.Fill(ds);
            }
            return ds;
        }

4.查询,返回DataTable

/// <summary>
        /// 查询,返回DataTable
        /// </summary>
        /// <param name="cmdText">Sql命令文本</param>
        /// <param name="data">参数数组</param>
        /// <returns>DataTable</returns>
        public static DataTable ExecuteDataTable(string cmdText, Dictionary<string, string> data)
        {
            var dt = new DataTable();
            using (SQLiteConnection connection = GetSQLiteConnection())
            {
                var command = new SQLiteCommand();
                PrepareCommand(command, connection, cmdText, data);
                SQLiteDataReader reader = command.ExecuteReader();
                dt.Load(reader);
            }
            return dt;
        }

5.返回一行数据 DataRow

/// <summary>
        /// 返回一行数据
        /// </summary>
        /// <param name="cmdText">Sql命令文本</param>
        /// <param name="data">参数数组</param>
        /// <returns>DataRow</returns>
        public static DataRow ExecuteDataRow(string cmdText, Dictionary<string, string> data)
        {
            DataSet ds = ExecuteDataset(cmdText, data);
            if (ds != null && ds.Tables.Count > 0 && ds.Tables[0].Rows.Count > 0)
                return ds.Tables[0].Rows[0];
            return null;
        }

6.执行数据库操作

/// <summary>
        /// 执行数据库操作
        /// </summary>
        /// <param name="cmdText">Sql命令文本</param>
        /// <param name="data">传入的参数</param>
        /// <returns>返回受影响的行数</returns>
        public static int ExecuteNonQuery(string cmdText, Dictionary<string, string> data)
        {
            using (SQLiteConnection connection = GetSQLiteConnection())
            {
                var command = new SQLiteCommand();
                PrepareCommand(command, connection, cmdText, data);
                return command.ExecuteNonQuery();
            }
        }

7.返回SqlDataReader对象

/// <summary>
        /// 返回SqlDataReader对象
        /// </summary>
        /// <param name="cmdText">Sql命令文本</param>
        /// <param name="data">传入的参数</param>
        /// <returns>SQLiteDataReader</returns>
        public static SQLiteDataReader ExecuteReader(string cmdText, Dictionary<string, string> data)
        {
            var command = new SQLiteCommand();
            SQLiteConnection connection = GetSQLiteConnection();
            try
            {
                PrepareCommand(command, connection, cmdText, data);
                SQLiteDataReader reader = command.ExecuteReader(CommandBehavior.CloseConnection);
                return reader;
            }
            catch
            {
                connection.Close();
                command.Dispose();
                throw;
            }
        }

8.返回结果集中的第一行第一列,忽略其他行或列

/// <summary>
        /// 返回结果集中的第一行第一列,忽略其他行或列
        /// </summary>
        /// <param name="cmdText">Sql命令文本</param>
        /// <param name="data">传入的参数</param>
        /// <returns>object</returns>
        public static object ExecuteScalar(string cmdText, Dictionary<string, string> data)
        {
            using (SQLiteConnection connection = GetSQLiteConnection())
            {
                var cmd = new SQLiteCommand();
                PrepareCommand(cmd, connection, cmdText, data);
                return cmd.ExecuteScalar();
            }
        }

9.分页查询

/// <summary>
        /// 分页查询
        /// </summary>
        /// <param name="recordCount">总记录数</param>
        /// <param name="pageIndex">页牵引</param>
        /// <param name="pageSize">页大小</param>
        /// <param name="cmdText">Sql命令文本</param>
        /// <param name="countText">查询总记录数的Sql文本</param>
        /// <param name="data">命令参数</param>
        /// <returns>DataSet</returns>
        public static DataSet ExecutePager(ref int recordCount, int pageIndex, int pageSize, string cmdText, string countText, Dictionary<string, string> data)
        {
            if (recordCount < 0)
                recordCount = int.Parse(ExecuteScalar(countText, data).ToString());
            var ds = new DataSet();
            using (SQLiteConnection connection = GetSQLiteConnection())
            {
                var command = new SQLiteCommand();
                PrepareCommand(command, connection, cmdText, data);
                var da = new SQLiteDataAdapter(command);
                da.Fill(ds, (pageIndex - 1) * pageSize, pageSize, "result");
            }
            return ds;
        }

10.重新组织数据库

当你从SQLite数据库中删除数据时, 未用的磁盘空间将会加入一个内部的“自由列表”中。
当你下次插入数据时,这部分空间可以重用。磁盘空间不会丢失, 但也不会返还给操作系统。
如果删除了大量数据,而又想缩小数据库文件占用的空间,执行 VACUUM 命令。 VACUUM 将会从头重新组织数据库

你可以在你的程序中约定一个时间间隔执行一次重新组织数据库的操作,节约空间

public void ResetDataBass()
        {
            using (SQLiteConnection conn = GetSQLiteConnection())
            {
                var cmd = new SQLiteCommand();

                if (conn.State != ConnectionState.Open)
                    conn.Open();
                cmd.Parameters.Clear();
                cmd.Connection = conn;
                cmd.CommandText = "vacuum";
                cmd.CommandType = CommandType.Text;
                cmd.CommandTimeout = 30;
                cmd.ExecuteNonQuery();
            }
        }

[转载]编写属于你的第一个Linux内核模块 - 博客 - 伯乐在线

mikel阅读(772)

[转载]编写属于你的第一个Linux内核模块 – 博客 – 伯乐在线.原文出处: LinuxVoice   译文出处: linux.cn。欢迎加入技术翻译小组

曾经多少次想要在内核游荡?曾经多少次茫然不知方向?你不要再对着它迷惘,让我们指引你走向前方……

内核编程常常看起来像是黑魔法,而在亚瑟 C 克拉克的眼中,它八成就是了。Linux内核和它的用户空间是大不相同的:抛开漫不经心,你必须小心翼翼,因为你编程中的一个bug就会影响到整个系统。 浮点运算做起来可不容易,堆栈固定而狭小,而你写的代码总是异步的,因此你需要想想并发会导致什么。而除了所有这一切之外,Linux内核只是一个很大 的、很复杂的C程序,它对每个人开放,任何人都去读它、学习它并改进它,而你也可以是其中之一。

学习内核编程的最简单的方式也许就是写个内核模块:一段可以动态加载进内核的代码。模块 所能做的事是有限的——例如,他们不能在类似进程描述符这样的公共数据结构中增减字段(LCTT译注:可能会破坏整个内核及系统的功能)。但是,在其它方 面,他们是成熟的内核级的代码,可以在需要时随时编译进内核(这样就可以摒弃所有的限制了)。完全可以在Linux源代码树以外来开发并编译一个模块(这 并不奇怪,它称为树外开发),如果你只是想稍微玩玩,而并不想提交修改以包含到主线内核中去,这样的方式是很方便的。

在本教程中,我们将开发一个简单的内核模块用以创建一个/dev/reverse设 备。写入该设备的字符串将以相反字序的方式读回(“Hello World”读成“World Hello”)。这是一个很受欢迎的程序员面试难题,当你利用自己的能力在内核级别实现这个功能时,可以使你得到一些加分。在开始前,有一句忠告:你的模 块中的一个bug就会导致系统崩溃(虽然可能性不大,但还是有可能的)和数据丢失。在开始前,请确保你已经将重要数据备份,或者,采用一种更好的方式,在 虚拟机中进行试验。

尽可能不要用root身份

默认情况下,/dev/reverse只有root可以使用,因此你只能使用sudo来运行你的测试程序。要解决该限制,可以创建一个包含以下内容的/lib/udev/rules.d/99-reverse.rules文件:

SUBSYSTEM=="misc", KERNEL=="reverse", MODE="0666"

别忘了重新插入模块。让非root用户访问设备节点往往不是一个好主意,但是在开发其间却是十分有用的。这并不是说以root身份运行二进制测试文件也不是个好主意。

模块的构造

由于大多数的Linux内核模块是用C写的(除了底层的特定于体系结构的部分),所以推 荐你将你的模块以单一文件形式保存(例如,reverse.c)。我们已经把完整的源代码放在GitHub上——这里我们将看其中的一些片段。开始时,我 们先要包含一些常见的文件头,并用预定义的宏来描述模块:

1
2
3
4
5
6
7
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Valentine Sinitsyn <valentine.sinitsyn@gmail.com>");
MODULE_DESCRIPTION("In-kernel phrase reverser");

这里一切都直接明了,除了MODULE_LICENSE():它不仅仅是一个标记。内核坚定地支持GPL兼容代码,因此如果你把许可证设置为其它非GPL兼容的(如,“Proprietary”[专利]),某些特定的内核功能将在你的模块中不可用。

什么时候不该写内核模块

内核编程很有趣,但是在现实项目中写(尤其是调试)内核代码要求特定的技巧。通常来讲,在没有其它方式可以解决你的问题时,你才应该在内核级别解决它。以下情形中,可能你在用户空间中解决它更好:

  • 你要开发一个USB驱动 —— 请查看libusb
  • 你要开发一个文件系统 —— 试试FUSE
  • 你在扩展Netfilter —— 那么libnetfilter_queue对你有所帮助。

通常,内核里面代码的性能会更好,但是对于许多项目而言,这点性能丢失并不严重。

由于内核编程总是异步的,没有一个main()函数来让Linux顺序执行你的模块。取而代之的是,你要为各种事件提供回调函数,像这个:

1
2
3
4
5
6
7
8
9
10
11
12
13
static int __init reverse_init(void)
{
    printk(KERN_INFO "reverse device has been registered\n");
    return 0;
}
static void __exit reverse_exit(void)
{
    printk(KERN_INFO "reverse device has been unregistered\n");
}
module_init(reverse_init);
module_exit(reverse_exit);

这里,我们定义的函数被称为模块的插入和删除。只有第一个的插入函数是必要的。目前,它们只是打印消息到内核环缓冲区(可以在用户空间通过dmesg命令访问);KERN_INFO是日志级别(注意,没有逗号)。__init__exit是属性 —— 联结到函数(或者变量)的元数据片。属性在用户空间的C代码中是很罕见的,但是内核中却很普遍。所有标记为__init的,会在初始化后释放内存以供重用(还记得那条过去内核的那条“Freeing unused kernel memory…[释放未使用的内核内存……]”信息吗?)。__exit表明,当代码被静态构建进内核时,该函数可以安全地优化了,不需要清理收尾。最后,module_init()module_exit()这两个宏将reverse_init()reverse_exit()函数设置成为我们模块的生命周期回调函数。实际的函数名称并不重要,你可以称它们为init()exit(),或者start()stop(),你想叫什么就叫什么吧。他们都是静态声明,你在外部模块是看不到的。事实上,内核中的任何函数都是不可见的,除非明确地被导出。然而,在内核程序员中,给你的函数加上模块名前缀是约定俗成的。

这些都是些基本概念 – 让我们来做更多有趣的事情吧。模块可以接收参数,就像这样:

1
# modprobe foo bar=1

modinfo命令显示了模块接受的所有参数,而这些也可以在/sys/module//parameters下作为文件使用。我们的模块需要一个缓冲区来存储参数 —— 让我们把这大小设置为用户可配置。在MODULE_DESCRIPTION()下添加如下三行:

1
2
3
static unsigned long buffer_size = 8192;
module_param(buffer_size, ulong, (S_IRUSR | S_IRGRP | S_IROTH));
MODULE_PARM_DESC(buffer_size, "Internal buffer size");

这儿,我们定义了一个变量来存储该值,封装成一个参数,并通过sysfs来让所有人可读。这个参数的描述(最后一行)出现在modinfo的输出中。

由于用户可以直接设置buffer_size,我们需要在reverse_init()来清除无效取值。你总该检查来自内核之外的数据 —— 如果你不这么做,你就是将自己置身于内核异常或安全漏洞之中。

1
2
3
4
5
6
7
8
9
static int __init reverse_init()
{
    if (!buffer_size)
        return -1;
    printk(KERN_INFO
        "reverse device has been registered, buffer size is %lu bytes\n",
        buffer_size);
    return 0;
}

来自模块初始化函数的非0返回值意味着模块执行失败。

导航

但你开发模块时,Linux内核就是你所需一切的源头。然而,它相当大,你可能在查找你所要的内容时会有困难。幸运的是, 在庞大的代码库面前,有许多工具使这个过程变得简单。首先,是Cscope —— 在终端中运行的一个比较经典的工具。你所要做的,就是在内核源代码的顶级目录中运行make cscope && cscope。Cscope和Vim以及Emacs整合得很好,因此你可以在你最喜爱的编辑器中使用它。

如果基于终端的工具不是你的最爱,那么就访问http://lxr.free-electrons.com吧。它是一个基于web的内核导航工具,即使它的功能没有Cscope来得多(例如,你不能方便地找到函数的用法),但它仍然提供了足够多的快速查询功能。

现在是时候来编译模块了。你需要你正在运行的内核版本头文件(linux-headers,或者等同的软件包)和build-essential(或者类似的包)。接下来,该创建一个标准的Makefile模板:

1
2
3
4
5
obj-m += reverse.o
all:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

现在,调用make来构建你的第一个模块。如果你输入的都正确,在当前目录内会找到reverse.ko文件。使用sudo insmod reverse.ko插入内核模块,然后运行如下命令:

1
2
$ dmesg | tail -1
[ 5905.042081] reverse device has been registered, buffer size is 8192 bytes

恭喜了!然而,目前这一行还只是假象而已 —— 还没有设备节点呢。让我们来搞定它。

混杂设备

在Linux中,有一种特殊的字符设备类型,叫做“混杂设备”(或者简称为“misc”)。它是专为单一接入点的小型设备驱动而设计的,而这正是我们所需要的。所有混杂设备共享同一个主设备号(10),因此一个驱动(drivers/char/misc.c)就可以查看它们所有设备了,而这些设备用次设备号来区分。从其他意义来说,它们只是普通字符设备。

要为该设备注册一个次设备号(以及一个接入点),你需要声明struct misc_device,填上所有字段(注意语法),然后使用指向该结构的指针作为参数来调用misc_register()。为此,你也需要包含linux/miscdevice.h头文件:

1
2
3
4
5
6
7
8
9
10
11
static struct miscdevice reverse_misc_device = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "reverse",
    .fops = &reverse_fops
};
static int __init reverse_init()
{
    ...
    misc_register(&reverse_misc_device);
    printk(KERN_INFO ...
}

这儿,我们为名为“reverse”的设备请求一个第一个可用的(动态的)次设备号;省略号表明我们之前已经见过的省略的代码。别忘了在模块卸下后注销掉该设备。

1
2
3
4
5
static void __exit reverse_exit(void)
{
    misc_deregister(&reverse_misc_device);
    ...
}

‘fops’字段存储了一个指针,指向一个file_operations结构(在Linux/fs.h中声明),而这正是我们模块的接入点。reverse_fops定义如下:

1
2
3
4
5
6
static struct file_operations reverse_fops = {
    .owner = THIS_MODULE,
    .open = reverse_open,
    ...
    .llseek = noop_llseek
};

另外,reverse_fops包含了一系列回调函数(也称之为方法),当用户空间代码打开一个设备,读写或者关闭文件描述符时,就会执行。如果你要忽略这些回调,可以指定一个明确的回调函数来替代。这就是为什么我们将llseek设置为noop_llseek(),(顾名思义)它什么都不干。这个默认实现改变了一个文件指针,而且我们现在并不需要我们的设备可以寻址(这是今天留给你们的家庭作业)。

关闭和打开

让我们来实现该方法。我们将给每个打开的文件描述符分配一个新的缓冲区,并在它关闭时释 放。这实际上并不安全:如果一个用户空间应用程序泄漏了描述符(也许是故意的),它就会霸占RAM,并导致系统不可用。在现实世界中,你总得考虑到这些可 能性。但在本教程中,这种方法不要紧。

我们需要一个结构函数来描述缓冲区。内核提供了许多常规的数据结构:链接列表(双联的),哈希表,树等等之类。不过,缓冲区常常从头设计。我们将调用我们的“struct buffer”:

1
2
3
4
struct buffer {
    char *data, *end, *read_ptr;
    unsigned long size;
};

data是该缓冲区存储的一个指向字符串的指针,而end指向字符串结尾后的第一个字节。read_ptrread()开始读取数据的地方。缓冲区的size是为了保证完整性而存储的 —— 目前,我们还没有使用该区域。你不能假设使用你结构体的用户会正确地初始化所有这些东西,所以最好在函数中封装缓冲区的分配和收回。它们通常命名为buffer_alloc()buffer_free()

static struct buffer buffer_alloc(unsigned long size) { struct buffer *buf; buf = kzalloc(sizeof(buf), GFP_KERNEL); if (unlikely(!buf)) goto out; … out: return buf; }

内核内存使用kmalloc()来分配,并使用kfree()来释放;kzalloc()的风格是将内存设置为全零。不同于标准的malloc(),它的内核对应部分收到的标志指定了第二个参数中请求的内存类型。这里,GFP_KERNEL是说我们需要一个普通的内核内存(不是在DMA或高内存区中)以及如果需要的话函数可以睡眠(重新调度进程)。sizeof(*buf)是一种常见的方式,它用来获取可通过指针访问的结构体的大小。

你应该随时检查kmalloc()的返回值:访问NULL指针将导致内核异常。同时也需要注意unlikely()宏的使用。它(及其相对宏likely())被广泛用于内核中,用于表明条件几乎总是真的(或假的)。它不会影响到控制流程,但是能帮助现代处理器通过分支预测技术来提升性能。

最后,注意goto语句。它们常常为认为是邪恶的,但是,Linux内核(以及一些其它系统软件)采用它们来实施集中式的函数退出。这样的结果是减少嵌套深度,使代码更具可读性,而且非常像更高级语言中的try-catch区块。

有了buffer_alloc()buffer_free()openclose方法就变得很简单了。

1
2
3
4
5
6
7
static int reverse_open(struct inode *inode, struct file *file)
{
    int err = 0;
    file->private_data = buffer_alloc(buffer_size);
    ...
    return err;
}

struct file是一个标准的内核数据结构,用以存储打开的文件的信息,如当前文件位置(file->f_pos)、标志(file->f_flags),或者打开模式(file->f_mode)等。另外一个字段file->privatedata用于关联文件到一些专有数据,它的类型是void *,而且它在文件拥有者以外,对内核不透明。我们将一个缓冲区存储在那里。

如果缓冲区分配失败,我们通过返回否定值(-ENOMEM)来为调用的用户空间代码标明。一个C库中调用的open(2)系统调用(如glibc)将会检测这个并适当地设置errno 。

学习如何读和写

“read”和“write”方法是真正完成工作的地方。当数据写入到缓冲区时,我们放弃之前的内容和反向地存储该字段,不需要任何临时存储。read方法仅仅是从内核缓冲区复制数据到用户空间。但是如果缓冲区还没有数据,revers_eread()会做什么呢?在用户空间中,read()调用会在有可用数据前阻塞它。在内核中,你就必须等待。幸运的是,有一项机制用于处理这种情况,就是‘wait queues’。

想法很简单。如果当前进程需要等待某个事件,它的描述符(struct task_struct存储‘current’信息)被放进非可运行(睡眠中)状态,并添加到一个队列中。然后schedule()就被调用来选择另一个进程运行。生成事件的代码通过使用队列将等待进程放回TASK_RUNNING状态来唤醒它们。调度程序将在以后在某个地方选择它们之一。Linux有多种非可运行状态,最值得注意的是TASK_INTERRUPTIBLE(一个可以通过信号中断的睡眠)和TASK_KILLABLE(一个可被杀死的睡眠中的进程)。所有这些都应该正确处理,并等待队列为你做这些事。

一个用以存储读取等待队列头的天然场所就是结构缓冲区,所以从为它添加wait_queue_headt read\queue字段开始。你也应该包含linux/sched.h头文件。可以使用DECLARE_WAITQUEUE()宏来静态声明一个等待队列。在我们的情况下,需要动态初始化,因此添加下面这行到buffer_alloc()

1
init_waitqueue_head(&buf->read_queue);

我们等待可用数据;或者等待read_ptr != end条件成立。我们也想要让等待操作可以被中断(如,通过Ctrl+C)。因此,“read”方法应该像这样开始:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static ssize_t reverse_read(struct file *file, char __user * out,
        size_t size, loff_t * off)
{
    struct buffer *buf = file->private_data;
    ssize_t result;
    while (buf->read_ptr == buf->end) {
        if (file->f_flags & O_NONBLOCK) {
            result = -EAGAIN;
            goto out;
        }
        if (wait_event_interruptible
        (buf->read_queue, buf->read_ptr != buf->end)) {
            result = -ERESTARTSYS;
            goto out;
        }
    }
...

我们让它循环,直到有可用数据,如果没有则使用wait_event_interruptible()(它是一个宏,不是函数,这就是为什么要通过值的方式给队列传递)来等待。好吧,如果wait_event_interruptible()被中断,它返回一个非0值,这个值代表-ERESTARTSYS。这段代码意味着系统调用应该重新启动。file->f_flags检查以非阻塞模式打开的文件数:如果没有数据,返回-EAGAIN

我们不能使用if()来替代while(),因为可能有许多进程正等待数据。当write方法唤醒它们时,调度程序以不可预知的方式选择一个来运行,因此,在这段代码有机会执行的时候,缓冲区可能再次空出。现在,我们需要将数据从buf->data 复制到用户空间。copy_to_user()内核函数就干了此事:

1
2
3
4
5
size = min(size, (size_t) (buf->end - buf->read_ptr));
   if (copy_to_user(out, buf->read_ptr, size)) {
       result = -EFAULT;
       goto out;
   }

如果用户空间指针错误,那么调用可能会失败;如果发生了此事,我们就返回-EFAULT。记住,不要相信任何来自内核外的事物!

1
2
3
4
5
   buf->read_ptr += size;
    result = size;
out:
    return result;
}

为了使数据在任意块可读,需要进行简单运算。该方法返回读入的字节数,或者一个错误代码。

写方法更简短。首先,我们检查缓冲区是否有足够的空间,然后我们使用copy_from_userspace()函数来获取数据。再然后read_ptr和结束指针会被重置,并且反转存储缓冲区内容:

1
2
3
4
buf->end = buf->data + size;
   buf->read_ptr = buf->data;
   if (buf->end > buf->data)
       reverse_phrase(buf->data, buf->end - 1);

这里, reverse_phrase()干了所有吃力的工作。它依赖于reverse_word()函数,该函数相当简短并且标记为内联。这是另外一个常见的优化;但是,你不能过度使用。因为过多的内联会导致内核映像徒然增大。

最后,我们需要唤醒read_queue中等待数据的进程,就跟先前讲过的那样。wake_up_interruptible()就是用来干此事的:

1
wake_up_interruptible(&buf->read_queue);

耶!你现在已经有了一个内核模块,它至少已经编译成功了。现在,是时候来测试了。

调试内核代码

或许,内核中最常见的调试方法就是打印。如果你愿意,你可以使用普通的printk() (假定使用KERN_Debug日志等级)。然而,那儿还有更好的办法。如果你正在写一个设备驱动,这个设备驱动有它自己的“struct device”,可以使用pr_Debug()或者dev_dbg():它们支持动态调试(dyndbg)特性,并可以根据需要启用或者禁用(请查阅Documentation/dynamic-Debug-howto.txt)。对于单纯的开发消息,使用pr_devel(),除非设置了DEBUG,否则什么都不会做。要为我们的模块启用DEBUG,请添加以下行到Makefile中:

CFLAGS_reverse.o := -DDEBUG

完了之后,使用dmesg来查看pr_debug()pr_devel()生成的调试信息。 或者,你可以直接发送调试信息到控制台。要想这么干,你可以设置console_loglevel内核变量为8或者更大的值(echo 8 /proc/sys/kernel/printk),或者在高日志等级,如KERN_ERR,来临时打印要查询的调试信息。很自然,在发布代码前,你应该移除这样的调试声明。

注意内核消息出现在控制台,不要在Xterm这样的终端模拟器窗口中去查看;这也是在内核开发时,建议你不在X环境下进行的原因。

惊喜,惊喜!

编译模块,然后加载进内核:

1
2
3
4
5
6
$ make
$ sudo insmod reverse.ko buffer_size=2048
$ lsmod
reverse 2419 0
$ ls -l /dev/reverse
crw-rw-rw- 1 root root 10, 58 Feb 22 15:53 /dev/reverse

一切似乎就位。现在,要测试模块是否正常工作,我们将写一段小程序来翻转它的第一个命令行参数。main()(再三检查错误)可能看上去像这样:

1
2
3
4
int fd = open("/dev/reverse", O_RDWR);
write(fd, argv[1], strlen(argv[1]));
read(fd, argv[1], strlen(argv[1]));
printf("Read: %s\n", argv[1]);

像这样运行:

1
2
$ ./test 'A quick brown fox jumped over the lazy dog'
Read: dog lazy the over jumped fox brown quick A

它工作正常!玩得更逗一点:试试传递单个单词或者单个字母的短语,空的字符串或者是非英语字符串(如果你有这样的键盘布局设置),以及其它任何东西。

现在,让我们让事情变得更好玩一点。我们将创建两个进程,它们共享一个文件描述符(及其内核缓冲区)。其中一个会持续写入字符串到设备,而另一个将读取这些字符串。在下例中,我们使用了fork(2)系统调用,而pthreads也很好用。我也省略打开和关闭设备的代码,并在此检查代码错误(又来了):

1
2
3
4
5
6
7
8
9
10
11
char *phrase = "A quick brown fox jumped over the lazy dog";
if (fork())
    /* Parent is the writer */
    while (1)
        write(fd, phrase, len);
else
    /* child is the reader */
    while (1) {
        read(fd, buf, len);
        printf("Read: %s\n", buf);
}

你希望这个程序会输出什么呢?下面就是在我的笔记本上得到的东西:

1
2
3
4
5
Read: dog lazy the over jumped fox brown quick A
Read: A kcicq brown fox jumped over the lazy dog
Read: A kciuq nworb xor jumped fox brown quick A
Read: A kciuq nworb xor jumped fox brown quick A
...

这里发生了什么呢?就像举行了一场比赛。我们认为readwrite是原子操作,或者从头到尾一次执行一个指令。然而,内核确实无序并发的,随便就重新调度了reverse_phrase()函数内部某个地方运行着的写入操作的内核部分。如果在写入操作结束前就调度了read()操作呢?就会产生数据不完整的状态。这样的bug非常难以找到。但是,怎样来处理这个问题呢?

基本上,我们需要确保在写方法返回前没有read方 法能被执行。如果你曾经编写过一个多线程的应用程序,你可能见过同步原语(锁),如互斥锁或者信号。Linux也有这些,但有些细微的差别。内核代码可以 运行进程上下文(用户空间代码的“代表”工作,就像我们使用的方法)和终端上下文(例如,一个IRQ处理线程)。如果你已经在进程上下文中和并且你已经得 到了所需的锁,你只需要简单地睡眠和重试直到成功为止。在中断上下文时你不能处于休眠状态,因此代码会在一个循环中运行直到锁可用。关联原语被称为自旋 锁,但在我们的环境中,一个简单的互斥锁 —— 在特定时间内只有唯一一个进程能“占有”的对象 —— 就足够了。处于性能方面的考虑,现实的代码可能也会使用读-写信号。

锁总是保护某些数据(在我们的环境中,是一个“struct buffer”实例),而且也常常会把它们嵌入到它们所保护的结构体中。因此,我们添加一个互斥锁(‘struct mutex lock’)到“struct buffer”中。我们也必须用mutex_init()来初始化互斥锁;buffer_alloc是用来处理这件事的好地方。使用互斥锁的代码也必须包含linux/mutex.h

互斥锁很像交通信号灯 —— 要是司机不看它和不听它的,它就没什么用。因此,在对缓冲区做操作并在操作完成时释放它之前,我们需要更新reverse_read()reverse_write()来获取互斥锁。让我们来看看read方法 —— write的工作原理相同:

1
2
3
4
5
6
7
8
9
static ssize_t reverse_read(struct file *file, char __user * out,
        size_t size, loff_t * off)
{
    struct buffer *buf = file->private_data;
    ssize_t result;
    if (mutex_lock_interruptible(&buf->lock)) {
        result = -ERESTARTSYS;
        goto out;
}

我们在函数一开始就获取锁。mutex_lock_interruptible()要么得到互斥锁然后返回,要么让进程睡眠,直到有可用的互斥锁。就像前面一样,_interruptible后缀意味着睡眠可以由信号来中断。

1
2
3
4
5
6
7
8
while (buf->read_ptr == buf->end) {
        mutex_unlock(&buf->lock);
        /* ... wait_event_interruptible() here ... */
        if (mutex_lock_interruptible(&buf->lock)) {
            result = -ERESTARTSYS;
            goto out;
        }
    }

下面是我们的“等待数据”循环。当获取互斥锁时,或者发生称之为“死锁”的情境时,不应该让进程睡眠。因此,如果没有数据,我们释放互斥锁并调用wait_event_interruptible()。当它返回时,我们重新获取互斥锁并像往常一样继续:

1
2
3
4
5
6
7
8
9
if (copy_to_user(out, buf->read_ptr, size)) {
        result = -EFAULT;
        goto out_unlock;
    }
    ...
out_unlock:
    mutex_unlock(&buf->lock);
out:
    return result;

最后,当函数结束,或者在互斥锁被获取过程中发生错误时,互斥锁被解锁。重新编译模块(别忘了重新加载),然后再次进行测试。现在你应该没发现毁坏的数据了。

接下来是什么?

现在你已经尝试了一次内核黑客。我们刚刚为你揭开了这个话题的外衣,里面还有更多东西供 你探索。我们的第一个模块有意识地写得简单一点,在从中学到的概念在更复杂的环境中也一样。并发、方法表、注册回调函数、使进程睡眠以及唤醒进程,这些都 是内核黑客们耳熟能详的东西,而现在你已经看过了它们的运作。或许某天,你的内核代码也将被加入到主线Linux源代码树中 —— 如果真这样,请联系我们!

自媒体为什么这么火?

mikel阅读(937)

互联网上信息量太大,人的本能就是对不熟悉的、不了解的新生事物总是否定,然后慢慢熟悉、了解才可以接受,自媒体的出现缩短了这一熟悉接受的过程,然后互联网上出现了百家争鸣的现象,大家都争先做自媒体,什么微信、微博、公众号、博客、网站、QQ空间,甚至连视频、音乐、YY语言只要能用的流行的互联网产品,大家都用来打造自媒体,发表自己的文章、声音、视频,草根儿要逆袭,你让传统媒体情何以堪啊?!

QQ截图20140626224042

这么多人搞自媒体,发布干货到底是为什么?!当雷锋吗?!显然不是,那为什么还有那么多人乐此不疲,大晚上不睡觉写干货分享呢?!

“天下熙熙,皆为利来;天下攘攘,皆为利往”

自媒体做的是个人品牌,是粉丝经济,是熟人的买卖,建的是“鱼塘”,同质化严重的世界,人们已经被传统的广告和各媒体的推广营销手段锻炼的火眼金睛,没点儿内涵的广告或者软文基本没啥效果,人们已经免疫,卖同一种商品的商家太多,我凭什么买你家的,不买他家的? 买得就是感觉,就是情感诉求和信任度!自媒体可以说是被传统媒体逼出来的!不一样的声音总是会引起注意,然后大家都会去问个究竟,你为什么要这么说?然后会得到重视并回复,一来二去成了朋友,然后分享下我买了什么商品,用了什么手机,自然而然的就得到关注,通俗点儿,你的家人发布的朋友圈信息,你总是会留意看下的,这就是信任度,有人的地方就有江湖,有人的地方也就有市场,既然大家都知道这个道理,那就 Just Do IT !

小伙伴儿们发挥自己的特长,做自媒体搞粉丝经济,走起!

分析了这么多,怎么做自媒体?说起来很简单,那就学 懂懂 写8年的日志就行了,让读者看着给钱,都是1200起价,不给也可以免费看,你跟懂懂讨价还价,你都不好意思,你白看人家那么多年的日志,你好意思吗?!给个钱人家也没强迫你,跟卖艺差不多,你看着给?!你再讨价还价那节操真的掉一地了!

这就是自媒体的力量,淘宝买个东西大家都还讨价还价,卖家都泪奔了,你就是死不松口,我凭什么不砍价,凭什么不给我便宜啊?!你遇到过,淘宝卖家让你看着给,给多少他都不嫌弃的吗?!结果你还不好意思给少了的情况吗? 自媒体因为啥火,都熟人了,空间日志评论里交流了多少次了,成朋友了,你给个1块钱,你都不好意思!自媒体走得是情感路线,不是冷冰冰的买卖!

归根结底,要做自媒体还是执行力,你能坚持每天都写日志,写8年吗?!

不多说了,仁者见仁智者见智, 更多信息可以订阅微信公众号:weikebao

qrcode_for_gh_e8f970702774_430

[转载]BrnShop开源网上商城第三讲:插件的工作机制 - 时间都去哪了 - 博客园

mikel阅读(1134)

[转载]BrnShop开源网上商城第三讲:插件的工作机制 – 时间都去哪了 – 博客园.

这几天BrnShop的开发工作比较多,所以这一篇文章来的晚了一些,还请大家见谅呀!还有通知大家一下BrnShop1.0.312版本已经发布,此版本添加了报表统计等新功能,需要源码的园友可以点此下载。好了,我们现在进入今天的正题。关于BrnShop插件内容比较多,所以我分成两篇文章来讲解,今天先讲第一部分内容:插件的工作机制。

  对于任意一种插件机制来说,基本上只要解决以下三个方面的问题,这个插件机制就算成功了。这三个方面如下:

  • 插件程序集的加载
  • 视图文件的路径和编译
  • 插件的部署

首先是插件程序集的加载(请仔细阅读,下面的坑比较多),最简单的方式就是直接将插件程序集复制到网站根目录下的bin文件夹(够简单吧!), 但是我们不推荐使用这种方式,因为这种方式导致插件的程序集和视图文件等内容分布在不同目录,为以后的维护带来不便。我们期望的是插件的所有内容都在同一 个目录中,以BrnShop的支付宝插件为例:

所有的插件文件都在“BrnShop.PayPlugin.Alipay”目录中,这样我们以后的删除,扩展等就方便多了。好了现在第一个坑来了, 那就是如何让ASP.NET加载此目录下的程序集文件?我们可能会想到使用web.config文件中probing节点来配置ASP.NET运行时探测 程序集的目录,但是很不幸它不管用(不信你可以试一下)。代码如下:


既然自动加载不行,那我们就手动加载吧。手动加载需要使用一个方法:System.Web.Compilation.BuildManager.AddReferencedAssembly(Assembly assembly),此方法的的MSDN解释如下图:

通过调用这个方法可以手动加载指定的程序集(就是它的参数)到我们的程序中。在调用这个方法时有两个坑需要注意,第一个坑是这个方法必须在Application_Start 事件发生前调用,针对这个坑我们可以使用微软在.NET4.0中提供的PreApplicationStartMethodAttribute性质(此性质的具体介绍大家可以看这篇文章:http://haacked.com/archive/2010/05/16/three-hidden-extensibility-gems-in-asp-net-4.aspx/)来解决这个问题。代码如下:

[assembly: PreApplicationStartMethod(typeof(BrnShop.Core.BSPPlugin), "Load")]

第二个坑是CLR会锁定程序集文件,所以如果我们直接读取此文件会导致异常,解决这个坑的方式是复制它的一个副本,然后不读取原程序集,而是读取程序集的副本并传入AddReferencedAssembly方法中。具体代码如下:

try
{
//复制程序集
File.Copy(dllFile.FullName, newDllFile.FullName, true);
}
catch
{
//在某些情况下会出现"正由另一进程使用,因此该进程无法访问该文件"错误,所以先重命名再复制
File.Move(newDllFile.FullName, newDllFile.FullName + Guid.NewGuid().ToString("N") + ".locked");
File.Copy(dllFile.FullName, newDllFile.FullName, true);
}
//加载程序集的副本到应用程序中
Assembly assembly = Assembly.Load(AssemblyName.GetAssemblyName(newDllFile.FullName));
//将程序集添加到当前应用程序域
BuildManager.AddReferencedAssembly(assembly);

不过这时又产生一个坑:此副本复制到哪个目录呢?我们期望这个副本能够保存下来,不必每次应用程序启动都要复制一次,熟悉asp.net编译的园友 们估计都会想到,这个目录就是应用程序域的DynamicDirectory目录,一般情况下这个目录位于C:\Windows \Microsoft.NET\Framework\v4.0.30319\Temporary ASP.NET Files\root\15a300ab\6af0b19a类似的目录,截图如下:

不过这时又产生了一个坑(哎,步步有坑呀!):权限问题,就是只有在Full Trust级别下才有操作此目录的权限,而在Medium Trust级别下我们没有操作此目录的权限。信任级别的MSDN解释如下:

针对这个坑我们只能根据不同的级别复制到不同的目录,Full Trust级别时复制到DynamicDirectory目录,Medium Trust级别时复制到一个影子目录(/Plugins/bin)代码如下:

DirectoryInfo copyFolder;
//根据当前的信任级别设置复制目录
if (WebHelper.GetTrustLevel() != AspNetHostingPermissionLevel.Unrestricted)//非完全信任级别
{
//shadowFolder就是"/Plugins/bin"影子目录
copyFolder = shadowFolder;
}
else//完全信任级别
{
copyFolder = new DirectoryInfo(AppDomain.CurrentDomain.DynamicDirectory);
}

在跳过上面众多坑后,我们终于长舒一口气,来到胜利的彼岸:插件的程序集文件能够正确加载了。下面附上插件加载的完整代码:

using System;
using System.IO;
using System.Web;
using System.Reflection;
using System.Web.Compilation;
using System.Collections.Generic;

[assembly: PreApplicationStartMethod(typeof(BrnShop.Core.BSPPlugin), "Load")]
namespace BrnShop.Core
{
///
/// BrnShop插件管理类
///

public class BSPPlugin
{
private static object _locker = new object();//锁对象

private static string _installedfilepath = "/App_Data/InstalledPlugin.config";//插件安装文件
private static string _pluginfolderpath = "/Plugins";//插件目录
private static string _shadowfolderpath = "/Plugins/bin";//插件影子目录

private static List _oauthpluginlist = new List();//开放授权插件列表
private static List _paypluginlist = new List();//支付插件列表
private static List _shippluginlist = new List();//配送插件列表
private static List _uninstalledpluginlist = new List();//未安装插件列表

///
/// 开放授权插件列表
///

public static List OAuthPluginList
{
get { return _oauthpluginlist; }
}
///
/// 支付插件列表
///

public static List PayPluginList
{
get { return _paypluginlist; }
}
///
/// 配送插件列表
///

public static List ShipPluginList
{
get { return _shippluginlist; }
}
///
/// 未安装插件列表
///

public static List UnInstalledPluginList
{
get { return _uninstalledpluginlist; }
}

///
/// 加载插件程序集到应用程序域中
///

public static void Load()
{
try
{
//插件目录
DirectoryInfo pluginFolder = new DirectoryInfo(IOHelper.GetMapPath(_pluginfolderpath));
if (!pluginFolder.Exists)
pluginFolder.Create();
//插件bin目录
DirectoryInfo shadowFolder = new DirectoryInfo(IOHelper.GetMapPath(_shadowfolderpath));
if (!shadowFolder.Exists)
{
shadowFolder.Create();
}
else
{
//清空影子复制目录中的dll文件
foreach (FileInfo fileInfo in shadowFolder.GetFiles())
{
fileInfo.Delete();
}
}

//获得安装的插件系统名称列表
List installedPluginSystemNameList = GetInstalledPluginSystemNameList();
//获得全部插件
List&lt;KeyValuePair&lt;FileInfo, PluginInfo&gt;&gt; allPluginFileAndInfo = GetAllPluginFileAndInfo(pluginFolder);
foreach (KeyValuePair&lt;FileInfo, PluginInfo&gt; fileAndInfo in allPluginFileAndInfo)
{
FileInfo pluginFile = fileAndInfo.Key;
PluginInfo pluginInfo = fileAndInfo.Value;

if (String.IsNullOrWhiteSpace(pluginInfo.SystemName))
throw new BSPException(string.Format("插件'{0}'没有\"systemName\", 请输入一个唯一的\"systemName\"", pluginFile.FullName));
if (pluginInfo.Type &lt; 0 || pluginInfo.Type &gt; 2)
throw new BSPException(string.Format("插件'{0}'不属于任何一种类型, 请输入正确的的\"type\"", pluginFile.FullName));

//加载插件dll文件
FileInfo[] dllFiles = pluginFile.Directory.GetFiles("*.dll", SearchOption.TopDirectoryOnly);
foreach (FileInfo dllFile in dllFiles)
{
//部署dll文件
DeployDllFile(dllFile, shadowFolder);
}

if (IsInstalledlPlugin(pluginInfo.SystemName, installedPluginSystemNameList))//安装的插件
{
//根据插件类型将插件添加到相应列表
switch (pluginInfo.Type)
{
case 0:
_oauthpluginlist.Add(pluginInfo);
break;
case 1:
_paypluginlist.Add(pluginInfo);
break;
case 2:
_shippluginlist.Add(pluginInfo);
break;
}
}
else//未安装的插件
{
_uninstalledpluginlist.Add(pluginInfo);
}
}
}
catch (Exception ex)
{
throw new BSPException("加载BrnShop插件时出错", ex);
}
}

///
/// 安装插件
///

///插件系统名称 public static void Install(string systemName)
{
lock (_locker)
{
if (string.IsNullOrWhiteSpace(systemName))
return;

//在未安装的插件列表中获得对应插件
PluginInfo pluginInfo = _uninstalledpluginlist.Find(x =&gt; x.SystemName.Equals(systemName, StringComparison.InvariantCultureIgnoreCase));

//当插件为空时直接返回
if (pluginInfo == null)
return;

//当插件不为空时将插件添加到相应列表
switch (pluginInfo.Type)
{
case 0:
_oauthpluginlist.Add(pluginInfo);
_oauthpluginlist.Sort((first, next) =&gt; first.DisplayOrder.CompareTo(next.DisplayOrder));
break;
case 1:
_paypluginlist.Add(pluginInfo);
_paypluginlist.Sort((first, next) =&gt; first.DisplayOrder.CompareTo(next.DisplayOrder));
break;
case 2:
_shippluginlist.Add(pluginInfo);
_shippluginlist.Sort((first, next) =&gt; first.DisplayOrder.CompareTo(next.DisplayOrder));
break;
}

//在未安装的插件列表中移除对应插件
_uninstalledpluginlist.Remove(pluginInfo);

//将新安装的插件保存到安装的插件列表中
List installedPluginSystemNameList = GetInstalledPluginSystemNameList();
installedPluginSystemNameList.Add(pluginInfo.SystemName);
SaveInstalledPluginSystemNameList(installedPluginSystemNameList);
}
}

///
/// 卸载插件
///

///插件系统名称 public static void Uninstall(string systemName)
{
lock (_locker)
{
if (string.IsNullOrEmpty(systemName))
return;

PluginInfo pluginInfo = null;
Predicate condition = x =&gt; x.SystemName.Equals(systemName, StringComparison.InvariantCultureIgnoreCase);
pluginInfo = _oauthpluginlist.Find(condition);
if (pluginInfo == null)
pluginInfo = _paypluginlist.Find(condition);
if (pluginInfo == null)
pluginInfo = _shippluginlist.Find(condition);

//当插件为空时直接返回
if (pluginInfo == null)
return;

//根据插件类型移除对应插件
switch (pluginInfo.Type)
{
case 0:
_oauthpluginlist.Remove(pluginInfo);
break;
case 1:
_paypluginlist.Remove(pluginInfo);
break;
case 2:
_shippluginlist.Remove(pluginInfo);
break;
}

//将插件添加到未安装插件列表
_uninstalledpluginlist.Add(pluginInfo);

//将卸载的插件从安装的插件列表中移除
List installedPluginSystemNameList = GetInstalledPluginSystemNameList();
installedPluginSystemNameList.Remove(pluginInfo.SystemName);
SaveInstalledPluginSystemNameList(installedPluginSystemNameList);
}
}

///
/// 编辑插件信息
///

///插件系统名称 ///插件友好名称 ///插件描述 ///插件排序 public static void Edit(string systemName, string friendlyName, string description, int displayOrder)
{
lock (_locker)
{
bool isInstalled = true;//是否安装
PluginInfo pluginInfo = null;
Predicate condition = x =&gt; x.SystemName.Equals(systemName, StringComparison.InvariantCultureIgnoreCase);

pluginInfo = _oauthpluginlist.Find(condition);
if (pluginInfo == null)
pluginInfo = _paypluginlist.Find(condition);
if (pluginInfo == null)
pluginInfo = _shippluginlist.Find(condition);

//当插件为空时直接返回
if (pluginInfo == null)
{
pluginInfo = _uninstalledpluginlist.Find(condition); ;
//当插件为空时直接返回
if (pluginInfo == null)
return;
else
isInstalled = false;
}

pluginInfo.FriendlyName = friendlyName;
pluginInfo.Description = description;
pluginInfo.DisplayOrder = displayOrder;

//将插件信息持久化到对应文件中
IOHelper.SerializeToXml(pluginInfo, IOHelper.GetMapPath("/Plugins/" + pluginInfo.Folder + "/PluginInfo.config"));

//插件列表重新排序
if (isInstalled)
{
switch (pluginInfo.Type)
{
case 0:
_oauthpluginlist.Sort((first, next) =&gt; first.DisplayOrder.CompareTo(next.DisplayOrder));
break;
case 1:
_paypluginlist.Sort((first, next) =&gt; first.DisplayOrder.CompareTo(next.DisplayOrder));
break;
case 2:
_shippluginlist.Sort((first, next) =&gt; first.DisplayOrder.CompareTo(next.DisplayOrder));
break;
}
}
else
{
_uninstalledpluginlist.Sort((first, next) =&gt; first.DisplayOrder.CompareTo(next.DisplayOrder));
}

}
}

///
/// 获得安装的插件系统名称列表
///

private static List GetInstalledPluginSystemNameList()
{
return (List)IOHelper.DeserializeFromXML(typeof(List), IOHelper.GetMapPath(_installedfilepath));
}

///
/// 保存安装的插件系统名称列表
///

///安装的插件系统名称列表 private static void SaveInstalledPluginSystemNameList(List installedPluginSystemNameList)
{
IOHelper.SerializeToXml(installedPluginSystemNameList, IOHelper.GetMapPath(_installedfilepath));
}

///
/// 获得全部插件
///

///插件目录 ///
private static List&lt;KeyValuePair&lt;FileInfo, PluginInfo&gt;&gt; GetAllPluginFileAndInfo(DirectoryInfo pluginFolder)
{
List&lt;KeyValuePair&lt;FileInfo, PluginInfo&gt;&gt; list = new List&lt;KeyValuePair&lt;FileInfo, PluginInfo&gt;&gt;();
FileInfo[] PluginInfoes = pluginFolder.GetFiles("PluginInfo.config", SearchOption.AllDirectories);
Type pluginType = typeof(PluginInfo);
foreach (FileInfo file in PluginInfoes)
{
PluginInfo info = (PluginInfo)IOHelper.DeserializeFromXML(pluginType, file.FullName);
list.Add(new KeyValuePair&lt;FileInfo, PluginInfo&gt;(file, info));
}

list.Sort((firstPair, nextPair) =&gt; firstPair.Value.DisplayOrder.CompareTo(nextPair.Value.DisplayOrder));
return list;
}

///
/// 判断插件是否已经安装
///

///插件系统名称 ///安装的插件系统名称列表 ///
private static bool IsInstalledlPlugin(string systemName, List installedPluginSystemNameList)
{
foreach (string name in installedPluginSystemNameList)
{
if (name.Equals(systemName, StringComparison.InvariantCultureIgnoreCase))
return true;
}
return false;
}

///
/// 部署程序集
///

///插件程序集文件 ////Plugins/bin目录 private static void DeployDllFile(FileInfo dllFile, DirectoryInfo shadowFolder)
{
DirectoryInfo copyFolder;
//根据当前的信任级别设置复制目录
if (WebHelper.GetTrustLevel() != AspNetHostingPermissionLevel.Unrestricted)//非完全信任级别
{
copyFolder = shadowFolder;
}
else//完全信任级别
{
copyFolder = new DirectoryInfo(AppDomain.CurrentDomain.DynamicDirectory);
}

FileInfo newDllFile = new FileInfo(copyFolder.FullName + "\\" + dllFile.Name);
try
{
File.Copy(dllFile.FullName, newDllFile.FullName, true);
}
catch
{
//在某些情况下会出现"正由另一进程使用,因此该进程无法访问该文件"错误,所以先重命名再复制
File.Move(newDllFile.FullName, newDllFile.FullName + Guid.NewGuid().ToString("N") + ".locked");
File.Copy(dllFile.FullName, newDllFile.FullName, true);
}

Assembly assembly = Assembly.Load(AssemblyName.GetAssemblyName(newDllFile.FullName));
//将程序集添加到当前应用程序域
BuildManager.AddReferencedAssembly(assembly);
}
}
}

在解决了插件程序集的加载问题后我们再来解决视图文件的路径和编译问题,由于插件目录不在传统View文件夹中,所以我们像普通视图那样返回插件路径,我们需要使用根目录路径来返回视图文件路径,以支付宝插件为例:

///
/// 配置
///

[HttpGet]
[ChildActionOnly]
public ActionResult Config()
{
ConfigModel model = new ConfigModel();

model.Partner = PluginUtils.GetPluginSet().Partner;
model.Key = PluginUtils.GetPluginSet().Key;
model.Seller = PluginUtils.GetPluginSet().Seller;
model.AllowRecharge = PluginUtils.GetPluginSet().AllowRecharge;

//插件视图文件路径必须以"~"开头
return View("~/Plugins/BrnShop.PayPlugin.Alipay/Views/AdminAlipay/Config.cshtml", model);
}

通过使用根目录路径,我们可以忽视一切路由匹配问题了。再来说说视图的编译问题,为了能够正确指导asp.net编译视图文件,我们需要在“/Plugins”文件夹(此文件夹中的web.cong能够覆盖所有插件目录)中添加一个web.config文件,并指定编译要求如下:


















这样我们的插件视图文件就能够正确编译了。

现在只剩下插件的部署问题了。如果是手动部署我们只需要将插件目录及其文件复制到”/Plugins”目录中就可以。如果是使用vs自动部署我们需要做以下几步配置:

第一步配置插件程序集的输出路径,通过在项目上点击右键选择属性进入,具体配置如下:

现在你生成一下解决方案就会发现插件程序集已经到”/Plugins”文件夹中。

第二步是筛选程序集,就是只输出插件程序集,其它的程序集(包括系统自带和引用的程序集)不输出。具体配置如下如图:

最后一步是输出内容文件,例如视图文件,具体配置如下图:

到此BrnShop的插件能够正常工作了。

PS:其实ASP.NET MVC插件的实现方式有许多种,大家可以google一下就会发现。而BrnShop之所以采用这种插件机制其实是服从于程序整体框架设计理念的。我们在 设计BrnShop框架之初确定的框架设计理念是:在不损失框架的扩展性,稳定性和性能的条件下,一切从简,直接,一目了然,返璞归真。最后奉上本人的框 架设计理念(如不喜,请轻喷!):

  • 高手和大神的区别:高手能够把简单的问题复杂化,大神能够把复杂的问题简单化。
  • 项目需要什么架构不是想出来的,而是在开发过程中为解决问题而引入的。不能因为存在或会某种设计模式就使用。

有对网上商城程序设计感兴趣的朋友,欢迎加入QQ群:235274151,大家可以交流下!

[转载]C# 读取 CSV 文件 - fiozhao - 博客园

mikel阅读(1292)

[转载]C# 读取 CSV 文件 – fiozhao – 博客园.

最近做一个C#项目要导入CSV文件中的数据到Oracle中,使用Aspose.Cells读取中文字段标题却乱码,表的最后多出几行null记录,而 且不是免费的,后来找到了NPOI,顾名思义,就是POI的.NET版本,POI是一套用Java写成的库,能够帮助开发者在没有安装微软Office的 情况下读写Office 97-2003的文件,支持的文件格式包括xls, doc,
ppt等。而且是免费的,学了半天,得出一个结论,杀鸡用牛刀。最后决定自已写一个读取及写入CSV文件的代码,效果非常好。现分享受应趣的朋友门。

using System;
using System.Data;
using System.IO;

namespace DBUtility
{
public static class CsvHelper
{
///
/// 写入CSV文件
///

///DataTable ///文件全名 /// 是否写入成功
public static Boolean SaveCSV(DataTable dt, string fullFileName)
{
Boolean r = false;
FileStream fs = new FileStream(fullFileName, System.IO.FileMode.Create, System.IO.FileAccess.Write);
StreamWriter sw = new StreamWriter(fs, System.Text.Encoding.Default);
string data = "";

//写出列名称
for (int i = 0; i &lt; dt.Columns.Count; i++)
{
data += dt.Columns[i].ColumnName.ToString();
if (i &lt; dt.Columns.Count - 1)
{
data += ",";
}
}
sw.WriteLine(data);

//写出各行数据
for (int i = 0; i &lt; dt.Rows.Count; i++)
{
data = "";
for (int j = 0; j &lt; dt.Columns.Count; j++)
{
data += dt.Rows[i][j].ToString();
if (j &lt; dt.Columns.Count - 1)
{
data += ",";
}
}
sw.WriteLine(data);
}

sw.Close();
fs.Close();

r = true;
return r;
}

///
/// 打开CSV 文件
///

///文件全名 /// DataTable
public static DataTable OpenCSV(string fullFileName)
{
return OpenCSV(fullFileName, 0, 0, 0, 0, true);
}

///
/// 打开CSV 文件
///

///文件全名 ///开始行 ///开始列 ///获取多少行 ///获取多少列 ///是有标题行 /// DataTable
public static DataTable OpenCSV(string fullFileName, Int16 firstRow = 0, Int16 firstColumn = 0, Int16 getRows = 0, Int16 getColumns = 0, bool haveTitleRow = true)
{
DataTable dt = new DataTable();
FileStream fs = new FileStream(fullFileName, System.IO.FileMode.Open, System.IO.FileAccess.Read);
StreamReader sr = new StreamReader(fs, System.Text.Encoding.Default);
//记录每次读取的一行记录
string strLine = "";
//记录每行记录中的各字段内容
string[] aryLine;
//标示列数
int columnCount = 0;
//是否已建立了表的字段
bool bCreateTableColumns = false;
//第几行
int iRow = 1;

//去除无用行
if (firstRow &gt; 0)
{
for (int i=1; i &lt; firstRow;i++)
{
sr.ReadLine();
}
}

// { ",", ".", "!", "?", ";", ":", " " };
string[] separators = { "," };
//逐行读取CSV中的数据
while ((strLine = sr.ReadLine()) != null)
{
strLine = strLine.Trim();
aryLine = strLine.Split(separators, System.StringSplitOptions.RemoveEmptyEntries);

if (bCreateTableColumns == false)
{
bCreateTableColumns = true;
columnCount = aryLine.Length;
//创建列
for (int i = firstColumn; i &lt; (getColumns == 0 ? columnCount : firstColumn+ getColumns); i++)
{
DataColumn dc
= new DataColumn(haveTitleRow == true ? aryLine[i] : "COL" + i.ToString());
dt.Columns.Add(dc);
}

bCreateTableColumns = true;

if (haveTitleRow == true)
{
continue;
}
}

DataRow dr = dt.NewRow();
for (int j = firstColumn; j &lt; (getColumns == 0 ? columnCount : firstColumn + getColumns); j++) { dr[j - firstColumn] = aryLine[j]; } dt.Rows.Add(dr); iRow = iRow + 1; if (getRows &gt; 0)
{
if (iRow &gt; getRows)
{
break;
}
}

}

sr.Close();
fs.Close();
return dt;
}
}
}

[转载].NET WinForm程序中给DataGridView表头添加下拉列表实现数据过滤 - Jaxu - 博客园

mikel阅读(1198)

[转载].NET WinForm程序中给DataGridView表头添加下拉列表实现数据过滤 – Jaxu – 博客园. 我们见过Excel中的数据过滤功能,可以通过点击表头上的下拉列表来实现数据的过滤,这个功能很实用,省去了我们需要在程序中单独设计数据的查询过滤模块,功能直接依赖于数据绑定控件DataGridView。先来看看Excel中的数据过滤功能。

  要想在DataGridView中实现类似于Excel的这种功能其实也并非难事。来看看msdn上的一篇文章,上面有详细的介绍,不过目前只有全英文的版本。http://msdn.microsoft.com/en-us/library/aa480727.aspx。里面提供的下载示例我这里也可以提供一份:DataGridViewAutoFilter.zip

  文章讲了很多有关如何实现数据过滤的知识,如果你有耐心可以通读一遍,应该能有不小的收获。其实这里面的原理就是我 们需要自定义一种DataGridViewColumn,它能支持用户通过点击表头上的下拉列表来实现DataGridView的数据过滤。自定义的 DataGridViewColumn可以继承自现有的DataGridViewTextBoxColumn类型,另外还需要自定义一个继承自 DataGridViewColumnHeaderCell的类型,它负责在DataGridView表头上呈现一个下拉列表,并完成数据过滤的选择功 能。下载上面的DataGridViewAutoFilter.zip压缩包,将里面对应编程语言中的 DataGridViewAutoFilterColumnHeaderCell.cs和 DataGridAutoFilterTextBoxColumn.cs两个文件加入到你的工程中。然后需要重新定义DataGridView中的列,如 果你是手动指定DataGridView的列,则需要在窗体的Designer.cs文件中手动修改与DataGridView列相关的代码;或者你也可 以通过程序动态指定DataGridView的列。将需要显示数据过滤的列的类型指定为 DataGridViewAutoFilterTextBoxColumn类型。另外在绑定DataGridView数据源时必须使用 BindingSource而不能使用如DataTable之类的普通数据源,这一点非常重要!在后面的代码展示中你将会看到为什么要这么做。

  这里是具体的例子:

public Form1()
{
InitializeComponent();

// create sequence
Item[] items = new Item[] { new Book{Id = 1, Price = 13.50, Genre = "Comedy", Author = "Jim Bob"},
new Book{Id = 2, Price = 8.50, Genre = "Drama", Author = "John Fox"},
new Movie{Id = 1, Price = 22.99, Genre = "Comedy", Director = "Phil Funk"},
new Movie{Id = 1, Price = 13.40, Genre = "Action", Director = "Eddie Jones"}};

var query = from i in items
orderby i.Price
select i;

DataTable table = query.CopyToDataTable();
BindingSource source = new BindingSource();
source.DataSource = table;

foreach (DataColumn col in table.Columns)
{
DataGridViewAutoFilterTextBoxColumn commonColumn = new DataGridViewAutoFilterTextBoxColumn();
commonColumn.DataPropertyName = col.ColumnName;
commonColumn.HeaderText = col.ColumnName;
commonColumn.Resizable = DataGridViewTriState.True;
this.dataGridView1.Columns.Add(commonColumn);
}

this.dataGridView1.DataSource = source;
}

  代码中的第16行将LINQ的查询结果转换成了DataTable对象,相关内容大家可以看我的另一篇文章“如何将LINQ查询到的结果由匿名类型var转换成DataTable对象”。 另外代码中将DataGridView的所有列的类型指定成了DataGridViewAutoFilterTextBoxColumn,使其能够支持自 定义的数据过滤功能。好了,现在运行你的应用程序,将会看到表头上有下拉列表的小箭头,点击它并选择下拉列表中的项便可实现DataGridView数据 的排序。是不是很酷啊?不过这里还有一个小问题,那就是用户如何知道我当前选择了哪个列的数据过滤,界面是不是应该给出相应的数据过滤信息呢?我们可以在 窗体的StatusStrip控件中添加一些Label标签用来显示这些信息:

  1. 显示用户当前选择了多少行。这个需要将DataGridView的SelectionMode属性设置成行选择模式即FullRowSelect。

  2. 显示当前DataGridView一共有多少行。

  3. 显示Filter的信息及应用数据过滤之后的总行数。

  4. 添加一个按钮或链接用于移除当前的Filter。

  来看看具体的实现代码及程序运行时的效果:

private void dataGridView1_SelectionChanged(object sender, EventArgs e)
{
int iCount = this.dataGridView1.SelectedRows.Count;
this.toolStripStatus_SelectedRows.Text = string.Format("{0} row{1} selected", iCount.ToString(), iCount &gt; 1 ? "s" : "");
}

private void dataGridView1_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e)
{
BindingSource data = this.dataGridView1.DataSource as BindingSource;
if (data == null || data.DataSource == null)
{
return;
}

/* Show total records number*/
// Retrieve the unfiltered row count by
// temporarily unfiltering the data.
data.RaiseListChangedEvents = false;
String oldFilter = data.Filter;
data.Filter = null;
int iTotalNum = data.Count;
this.toolStripStatus_Total.Text = string.Format("Total of {0} record{1}.", iTotalNum.ToString(), iTotalNum &gt; 1 ? "s" : "");
data.Filter = oldFilter;
data.RaiseListChangedEvents = true;

/* Show filter information.*/
int iFilterNum = data.Count;
string filter = data.Filter;
if (String.IsNullOrEmpty(filter))
{
this.toolStripStatus_Separator2.Visible = false;
this.toolStripStatus_Filter.Visible = false;
this.toolStripStatus_ShowAll.Visible = false;
}
else
{
this.toolStripStatus_Separator2.Visible = true;
this.toolStripStatus_Filter.Visible = true;
this.toolStripStatus_ShowAll.Visible = true;
this.toolStripStatus_Filter.Text = string.Format("{0} record{1} found.", iFilterNum.ToString(), iFilterNum &gt; 1 ? "s" : "");
this.toolStripStatus_Filter.Text += " (Filter: " + filter + ")";
}
}

private void toolStripStatus_ShowAll_Click(object sender, EventArgs e)
{
DataGridViewAutoFilterColumnHeaderCell.RemoveFilter(this.dataGridView1);
}

  1. 当前用户选择的总行数。

  2. DataGridView中一共有多少行。

  3. Filter的信息及使用Filter之后的数据行数。

  4. 用于移除Filter的链接。

  代码中一共是三个事件,dataGridView1_SelectionChanged事件用于在 DataGridView行被选择时触发,用来更新StatusStrip中当前用户选择的总行 数;dataGridView1_DataBindingComplete事件在DataGridView数据完成绑定时触发,用来更新 StatusStrip中Filter的信息及使用Filter之后的数据行数,以及DataGridView的数据总行数,注意其中将 BindingSource的RaiseListChangedEvents设置为false以取得DataGridView数据源中的真实数据行数,之 后再将其设置为true以获取到Filter的相关信息;toolStripStatus_ShowAll_Click事件为用户点击Show All链接时触发,用于移除DataGridView中的Filter。

  这里是完整的代码:WindowsFormsApplication2.zip

[转载]DataTable与DataGridView绑定 - 左眼微笑右眼泪 - 博客园

mikel阅读(1044)

[转载]DataTable与DataGridView绑定 – 左眼微笑右眼泪 – 博客园.

在很多C/S程序中,一般都需要把数据以DataGridView显示出来,有时可以直接把得到的实体集合绑定到DataGridView上面,但更多的 时候,我们是把DataTable绑定到这个控件上面,在绑定之前,我们需要对DataGrdiView进行初始化。一般可以用如下的代码进行初始化:

dgv.Columns.Add("PRODUCTTYPE", "类型");

dgv.Columns["PRODUCTTYPE"].DataPropertyName = "PRODUCTTYPE";

dgv.Columns["PRODUCTTYPE"].Width = 160;

dgv.Columns.Add("ALIAS", "别名");

dgv.Columns["ALIAS"].DataPropertyName = "ALIAS";

dgv.Columns["ALIAS"].Width = 200;

dgv.Columns.Add("CLASSIFICATION", "类别");

dgv.Columns["CLASSIFICATION"].DataPropertyName = "CLASSIFICATION";

dgv.Columns["CLASSIFICATION"].Width = 200;

dgv.Columns["CLASSIFICATION"].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;

一般第一列都是显示一个图片,后面的列就要和你的DataTable中的字段对应。需要注意的是,在创建新列的时候,括号里面的英文名称一定是你DataTable中的列名,中文名称是你想在界面上显示的名称。最后有一句话dgv.Columns[“CLASSIFICATION”].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;,这个意思是设置最后一列为自动填充模式。因为每一列我们都设置了宽度,当窗 体最大化后,所以的列宽加起来没有窗体宽,那么DataGridView后面就会空出一片出来,把最后一列设置成了自动填充模式后,当窗体最大化时,最后 一列的宽度会自动增加,填充其他的空白区域。

另外,还有一种初始化的方法,也可以使用,代码如下:

DataGridViewTextBoxColumn colProductType = new DataGridViewTextBoxColumn();

colProductType.DataPropertyName = strPRODUCTTYPE;

colProductType.Name = strPRODUCTTYPE;

colProductType.HeaderText = "类型";

colProductType.Width = 160;

 

DataGridViewTextBoxColumn colAlias = new DataGridViewTextBoxColumn();

colAlias.DataPropertyName = strALIAS;

colAlias.Name = strALIAS;

colAlias.HeaderText = "别名";

colAlias.Width = 200;

 

DataGridViewTextBoxColumn colClassification = new DataGridViewTextBoxColumn();

colClassification.DataPropertyName = strCLASSIFICATION;

colClassification.Name = strCLASSIFICATION;

colClassification.HeaderText = "类别";

colClassification.Width = 200;

 

dgv.Columns.AddRange(new DataGridViewColumn[] { 

   colImg, 

   colProductType, 

   colAlias

   });

当然在这种方法中,strPRODUCTTYPE,strALIAS,strCLASSIFICATION三个变量分别需要提前定义好,它们的值也就是需要绑定的DataTable的列的值。