10.7 技术解惑
10.7.1 QueryExtender控件的用法
在ASP.NET程序中,QueryExtender控件是一个过滤控件,可以对数据库中的数据进行筛选。对于创建数据驱动的网页的开发人员,一项十分常见的任务就是筛选数据。该任务的传统实现执行方法是在数据源控件中生成Where子句。这种方法十分复杂,而且在某些情况下,通过Where语法无法充分利用基础数据库的全部功能。为了简化筛选操作,ASP.NET 4.5中增加了QueryExtender控件。
在开发应用中,可以将QueryExtender控件添加到EntityDataSource 或 LinqDataSource 控件中,以筛选这些控件返回的数据。QueryExtender控件依赖于LINQ,但用户无需了解如何编写LINQ查询即可使用该查询扩展程序。QueryExtender 控件支持多种筛选选项,如表10-1所示。
表10-1 QueryExtender的筛选选项
术 语 |
定 义 |
---|---|
SearchExpression |
搜索一个或多个字段中的字符串值,并将这些值与指定的字符串值进行比较。 |
RangeExpression |
在一个或多个字段中搜索由一对值指定的范围内的值 |
PropertyExpression |
对指定的值与字段中的属性值进行比较。如果表达式的计算结果为True,则返回所检查的数据 |
OrderByExpression |
按指定的列和排序方式对数据进行排序 |
CustomExpression |
调用一个函数,用于定义页面中的自定义筛选器 |
筛选操作通过仅显示满足指定条件的记录,从数据源中排除数据。通过筛选,可以在不影响数据集中的数据的情况下以多种方式查看这些数据。
筛选通常要求用户创建Where子句,以应用于查询数据源的命令。但是,LinqDataSource控件的Where属性并不公开LINQ中提供的全部功能。为了更便于筛选数据,ASP.NET 4.5提供了QueryExtender控件,该控件可通过声明性语法从数据源中筛选出数据。使用QueryExtender控件有以下优点。
(1)与编写Where子句相比,可提供功能更丰富的筛选表达式。
(2)提供一种LinqDataSource和EntityDataSource控件均可使用的查询语言。例如,如果将QueryExtender与这些数据源控件配合使用,则可以在网页中提供搜索功能,而不必编写特定于模型的Where子句或eSQL语句。
(3)可以与LinqDataSource或EntityDataSource 控件配合使用,或与第三方数据源配合使用。
(4)支持多种可单独和共同使用的筛选选项。
10.7.2 并行运算中的多线程
从ASP.NET 4.0开始,ASP.NET提供了新的并行库以及新的并行编程模式和编程思维方式。很多读者可能觉得在日常的编程中,ASP.NET程序员使用多线程编程的不是很多。其实这种感觉是错误的,我们无时无刻不在享受多线程的优势。首先,Web服务器环境就是一个多线程环境,每一个请求都是独立的线程。如果没有多线程,则很难想象只能同步处理一个请求的Web服务器有什么用。同样道理,数据库也是一个多线程环境。对于 Windows应用程序的程序员来说,恐怕很难不接触多线程,最简单的场景就是新开启线程去完成一些耗时的操作,这样就可以避免UI停止响应,操作结束后再把操作结果应用在主线程的控件上。虽然这样的应用是多线程,甚至很多程序员习惯于什么操作都新开启一个线程去实现。笔者觉得这样的多线程应用的思维还停留在单核时代,在多核时代确实可以让任务实际地并行执行而不是看上去并行执行。
进程和线程的基本概念不用多说,自然我们能理解一个进程至少包含一个线程。通过在一个进程中开启多个线程,就可以让一个程序在同一时间看上去能做多件事情,如可以在接受用户响应的,同时进行一些计算。在以前处理器往往只有一个核心,也就是说在同一时间处理器只能做一件事情。那么怎么实现多个线程同时执行呢?其实这个“同时”只是表面上看上去的“同时”,实质上多个线程依次占用处理器的若干时间片,大家轮流使用其资源。由于这个时间片非常短,所以在一个长的时间内看起来似乎是几个线程同时得到了执行。
举一个生动的例子,经常看到有一些画家能同时在一个画布上画两个不同的图像,如一只手画物,另一只手画房子,最后一起完成这幅画。但仔细观察即会发现,他是两只手分别拿了一只画笔,在这里画一笔,在那里画一笔,在同一时刻其实只有一只笔在画。这个画家应该也像普通人一样是“单核”的,只是线程切换比较快罢了。我们经常在打电话的时候和网友进行聊天,在同一时间做两件事情,但是这样很费脑子,在打字前要回忆一下刚才聊天的内容,然后输入聊天的文字;然后再去回想一下刚才电话那边说了什么,在电话中回复他一句,这种回忆的工作其实就是准备线程的上下文,交给大脑去处理。虽然同一时间是做了两件事情,但是这个上下文的准备工作也浪费了点时间。如果在打电话、网络聊天的同时在去做第三件事情,如看电影等,估计就不行了。所以,线程也不能开启太多,特别对于人脑来说。但是对于电脑处理器来说就不一样了,你只要准备好数据和指令它执行就是了,至于是几件事情它并不关心,每时每刻地执行指令也完全没问题,当然也可以让它闲着。
10.7.3 并行循环的中断和跳出
在并行循环过程中,偶尔也会需要中断循环或跳出循环。例如,下面的代码演示了跳出循环的两种方法:Stop和Break,其中LoopState是循环状态的参数。
/// <summary>
/// 中断Stop
/// </summary>
private void Demo5()
{
List<int> data = Program.Data;
Parallel.For(0, data.Count, (i, LoopState) =>
{
if (data[i] > 5)
LoopState.Stop();
Thread.Sleep(500);
Console.WriteLine(data[i]);
});
Console.WriteLine("Stop执行结束。");
}
/// <summary>
/// 中断Break
/// </summary>
private void Demo6()
{
List<int> data = Program.Data;
Parallel.ForEach(data, (i, LoopState) =>
{
if (i > 5)
LoopState.Break();
Thread.Sleep(500);
Console.WriteLine(i);
});
Console.WriteLine("Break执行结束。");
}
执行效果如图10-3所示。
图10-3 执行效果
由此可见,使用Stop会立即停止循环,使用Break会执行完毕所有符合条件的项再停止循环。
10.7.4 在并行循环中为数组/集合添加项
在ASP.NET应用中,遍历一个数组内的资源的场景比较常见。为了遍历资源,通常会使用如下的代码:
private void Demo7()
{
List<int> data = new List<int>();
Parallel.For(0, Program.Data.Count, (i) =>
{
if (Program.Data[i] % 2 == 0)
data.Add(Program.Data[i]);
});
Console.WriteLine("执行完成For.");
}
private void Demo8()
{
List<int> data = new List<int>();
Parallel.ForEach(Program.Data, (i) =>
{
if (Program.Data[i] % 2 == 0)
data.Add(Program.Data[i]);
});
Console.WriteLine("执行完成ForEach.");
}
对于上述代码,乍看起来应该是没有问题的,但是多次运行后会发现,偶尔也会出现错误,如图10-4所示。
图10-4 错误提示
造成上述错误的原因是List是非线程安全的类,需要在并行循环体内使用System.Collections.Concurrent命名空间下的类型。命名空间System.Collections.Concurrent中主要类的具体说明如表10-2所示。
表10-2 命名空间System.Collections.Concurrent中主要类
类 |
说 明 |
---|---|
BlockingCollection<T> |
为实现IProducerConsumerCollection<T> 的线程安全集合提供阻止和限制功能 |
ConcurrentBag<T> |
表示对象的线程安全的无序集合 |
ConcurrentDictionary<TKey, TValue> |
表示可由多个线程同时访问的键值对的线程安全集合 |
ConcurrentQueue<T> |
表示线程安全的先进先出(FIFO)集合 |
ConcurrentStack<T> |
表示线程安全的后进先出(LIFO)集合 |
OrderablePartitioner<TSource> |
表示将一个可排序数据源拆分成多个分区的特定方式 |
Partitioner |
提供针对数组、列表和可枚举项的常见分区策略 |
Partitioner<TSource> |
表示将一个数据源拆分成多个分区的特定方式 |
那么上面的代码可以修改为,增加了ConcurrentQueue和ConcurrentStack的最基本操作。
/// <summary>
/// 并行循环操作集合类,集合内只取5个对象
/// </summary>
private void Demo7()
{
ConcurrentQueue<int> data = new ConcurrentQueue<int>();
Parallel.For(0, Program.Data.Count, (i) =>
{
if (Program.Data[i] % 2 == 0)
data.Enqueue(Program.Data[i]);//将对象加入到队列末尾
});
int R;
while (data.TryDequeue(out R))//返回队列中开始处的对象
{
Console.WriteLine(R);
}
Console.WriteLine("执行完成For.");
}
/// <summary>
/// 并行循环操作集合类
/// </summary>
private void Demo8()
{
ConcurrentStack<int> data = new ConcurrentStack<int>();
Parallel.ForEach(Program.Data, (i) =>
{
if (Program.Data[i] % 2 == 0)
data.Push(Program.Data[i]);//将对象压入栈中
});
int R;
while (data.TryPop(out R))//弹出栈顶对象
{
Console.WriteLine(R);
}
Console.WriteLine("执行完成ForEach.");
}
这样就解决了返回一个序列的问题。由此可见,在并行循环内重复操作的对象,必须是Thread-Safe(线程安全)的。集合类的线程安全对象全部在System.Collections.Concurrent命名空间下。
10.7.5 对SEO的改进
搜索引擎优化(SEO)对任何面向公众的网站来说都是非常重要的,现在网站很大比例的流量来自搜索引擎。ASP.NET 4.5包含很多新的运行时特性,可以帮助用户进一步优化网站。其中一些新特性包含。
- 新的Page.MetaKeywords和Page.MetaDescription属性。
- 针对ASP.NET Web Forms的新URL导向支持。
- 新的Response.RedirectPermanent( )方法。
在接下来的内容中,将简单介绍利用这些特性来进一步提高搜索引擎相关性的知识。
(1)Page.MetaKeywords和Page.MetaDescription属性
改进网页搜索相关性的一个简单建议是,确定总是在HTML中的<head>部分输出相关的“keywords”(关键词)和“description”(描述)<meta>标识。例如:
在ASP.NET 4.5 Web Forms中,一个很好的改进是在Page类中增加了两个新属性:MetaKeywords和MetaDescription,它们使得在后台代码类中用编程的手法设置这些值更容易,也更干净。
ASP.NET 4.5的<head>服务器控件现在会检索这些值,然后在输出网页的<head>部分时使用它们。这个行为在使用母版页的场景中尤其有用,<head>是在.master文件中,与含有特定页面内容的.aspx文件是分开的。此时就可以在.aspx页面中设置新的MetaKeywords和MetaDescription属性,它们的值会自动地由母版页中的<head>控件来显示。
下面的代码演示了如何在Page_Load( )事件处理函数中,使用编程手法设置MetaKeywords和MetaDescription属性的过程。
除了在后台代码中使用编程手法设置MetaKeywords和MetaDescription属性外,还可以在.aspx网页顶部的@Page指令中使用声明的方式来设置,下面的代码就演示了这一设置过程。
这就像我们能预期的那样,如果使用编程手法设置这些属性值,则会替代在<head>部分或@Page指令中声明设置的任何值。
(2)ASP.NET Web Forms中的URL导向
URL导向是最先在ASP.NET 3.5 SP1中引进的一个功能,已为ASP.NET MVC应用所用,用于呈现干净的、SEO友好的“Web 2.0”URL。URL导向让配置一个应用来接受并不映射到物理文件的请求URL,我们可以使用导向来定义对用户来说语义上更具含义的URL,这些URL有助于搜索引擎优化(SEO)。
例如,一个显示产品分类的传统网页的URL也许看上去会是这样的:
http://www.mysite.com/products.aspx?category=software
通过使用ASP.NET 4.5中的URL导向引擎现在可以配置应用来接受下面这样的URL来显示同样的信息:
http://www.mysite.com/products/software
在ASP.NET 4.5中,像上面这样的URL现在可以映射到ASP.NET MVC控制器类,也可以映射到基于ASP.NET Web Forms的网页。甚至可以有一个应用,同时含有 Web Forms 和 MVC控制器,使用单一一套导向规则在它们之间映射URL。
(3)Response.RedirectPermanent( )方法
在ASP.NET应用中,开发人员经常使用Response.Redirect( )方法,用编程的手法将对老的URL的请求转到新的URL上。但许多开发人员没有意识到的是,Response.Redirect()方法发送的是一个HTTP 302 Found(临时转向)回复,会在用户尝试访问老的URL时,导致多余的HTTP往返。搜索引擎一般不会跟随多个重新转向跳转,这意味着使用一个临时转向会负面影响你的网页排名。
ASP.NET 4.5引进了一个新的Response.RedirectPermanent(string url)辅助方法,可以用来做一个HTTP 301(永久性重定向)重新定向。这会导致能识别永久性重新定向的搜索引擎,和其他用户代理保存和使用与内容相关联的新URL。这会使你的内容编入索引,你的搜索引擎页面排名得到提高。
下面的代码是使用新的Response.RedirectPermanent()方法重新定向到特定URL的一个例子。
ASP.NET 4.5还引进了新的Response.RedirectToRoute(string routeName)和Response. RedirectToRoute Permanent(string routeName)辅助方法,可以用来通过URL导向引擎做临时或永久性的重新定向。下面的代码演示了发出临时和永久性的重新定向到注册在URL导向系统中的具体路径的(该路径接受一个category参数)方法。
可以同时针对基于ASP.NET Web Forms以及基于ASP.NET MVC的URL使用上面的路径和方法。由此可见,ASP.NET 4.5包含了大量的特性改进来方便建造极致SEO的面向公众的网站。