Sql: Structured Query Language

Sql server的组成

  • 主要数据库文件:.mdf 特点:有且只有一个
  • 次要数据库文件:.ndf 特点:任意个
  • 日志数据库文件:.ldf 特点:至少一个

操作数据库

1
2
3
4
5
创建数据库:create databse 库名
查看所有数据库:exec sp_helpdb
查看当前数据库:exec sp_helpdb 库名
使用数据库:use 库名
删除数据库:drop database 库名 ps:正在使用的数据库无法删除

基础语法

创建表

1
2
3
4
5
CREATE TABLE [Student] (
[ID] INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
[Name] NVARCHAR(20),
[Age] INT,
[Sex] INT);

查看所有表

exec sp_help

查看当前表

exec sp_help 表名

修改表结构

1
2
3
4
5
6
7
8
9
10
11
12
13
-- 1,增加一列语法
alter table 表名 add 字段名 数据类型

-- 2,修改一列语法
alter table 表名 alter column 字段名 数据类型

-- 3,删除一列语法
alter table 表名 drop column 字段名

-- 4,删除表语法
drop table 表名 (有日志)

truncate table 表名称(没有日志)

操作表数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
-- 1,完全插入数据
insert into 表名 (字段名1,字段名2...) values(值1,值2...)

-- 2,省略插入数据
insert into 表名 values(值1,值2...)

-- 3,多行插入
insert into 表名 values(值1,值2...),(值1,值2...)

-- 4,查看所有记录
select * from 表名

-- 5,修改记录
update 表名 set 字段名=值 [where条件]

-- 6,删除一条记录
delete from 表名 where 条件

-- 7,清空表所有记录
delete from 表名

运算符

and , or ,not

sql 查询

  • 1,符合条件语法

    1
    select 字段名 from 表名 [where 条件]
  • 2,…到…之间

    1
    2
    3
    4
    5
    and ... or
    between ... and
    --例句:查询23岁到25岁之间的学生
    select * from student where age>=23 and age<=25
    select * from student where age between 23 and 25
  • 3,去重

    1
    select distinct 字段名 from 表名
  • 4,排序

    语法: order by + 字段名 asc升序(默认) desc降序

  • 5,列别名 as

  • 6,模糊查询 like

  • 7, 联合查询 join

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    -- 1,交叉查询
    select 字段名 from1 cross join2 [where 条件]

    -- 2,内连接查询
    select 字段名 from1 inner join2 on 联合条件 [where 条件]

    --外连接
    -- 3,左外连接
    select 字段名 from1 left join2 on 联合条件 [where 条件]

    -- 4,右外连接
    select 字段名 from1 right join2 on 联合条件 [where 条件]

    -- 5,全外连接
    select 字段名 from1 full join2 on 联合条件 [where 条件]
  • 8,嵌套查询

    1
    2
    3
    4
    in() 在...范围之内的
    not in() 不在...范围之内的
    exists 存在
    not exists 不存在
  • 9,分组 group by

系统函数

1,统计(聚合)函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
-- 1,AVG 返回指定组中的平均值,空值被忽略。
select prd_no,avg(qty) from sales group by prd_no

-- 2,COUNT 返回指定组中项目的数量
select count(prd_no) from sales

-- 3, MAX 返回指定数据的最大值。
select prd_no,max(qty) from sales group by prd_no

-- 4,MIN 返回指定数据的最小值。
select prd_no,min(qty) from sales group by prd_no

-- 5,SUM 返回指定数据的和,只能用于数字列,空值被忽略。
select prd_no,sum(qty) from sales group by prd_no

-- 6,COUNT_BIG 返回指定组中的项目数量,
-- 与COUNT函数不同的是COUNT_BIG返回bigint值,而COUNT返回的是int值。
select count_big(prd_no) from sales

-- 7,GROUPING 产生一个附加的列,当用CUBE或ROLLUP运算符添加行时,
-- 输出值为1.当所添加的行不是由CUBE或ROLLUP产生时,输出值为0.
select prd_no,sum(qty),grouping(prd_no) from sales group by prd_no with rollup

-- 8, BINARY_CHECKSUM 返回对表中的行或表达式列表计算的二进制校验值,用于检测表中行的更改。
select prd_no,binary_checksum(qty) from sales group by prd_no

-- 9,CHECKSUM_AGG 返回指定数据的校验值,空值被忽略。
select prd_no,checksum_agg(binary_checksum(*)) from sales group by prd_no

-- 10,CHECKSUM 返回在表的行上或在表达式列表上计算的校验值,用于生成哈希索引。

-- 11,STDEV 返回给定表达式中所有值的统计标准偏差。
select stdev(prd_no) from sales

-- 12,STDEVP 返回给定表达式中的所有值的填充统计标准偏差。
select stdevp(prd_no) from sales

-- 13, VAR 返回给定表达式中所有值的统计方差。
select var(prd_no) from sales

--14,VARP 返回给定表达式中所有值的填充的统计方差。
select varp(prd_no) from sales

2,日期函数

  • 1, getDate():获取当前时间

  • 2,Dateadd():增加时间

  • 3,datediff(datepart,startdate,enddate)

    参考:SQL Server DATEDIFF() 函数

    SELECT DATEDIFF(day,’2008-12-29’,’2008-12-30’) AS DiffDate
    输出:1

    startdate 和 enddate 参数是合法的日期表达式。
    datepart 参数可以是下列的值:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    date part |缩写
    年:yy, yyyy
    季度:qq, q
    月:mm, m
    年中的日:dy, y
    日:dd, d
    周:wk, ww
    星期:dw, w
    小时:hh
    分钟:mi, n
    秒:ss, s
    毫秒:ms
    微妙:mcs
    纳秒:ns

3,数学函数

1
2
3
4
5
abs():取绝对值
round():四舍五入
floor():函数返回小于或等于所给数字表达式的最大整数
ceiling():函数返回大于或等于所给数字表达式的最小整数
sqrt():开平方根

4,字符串函数

1
2
3
4
5
6
7
8
9
10
left():左截串
right():右截串
ltrim():去左空格
rtrim():去右空格
replace:(字符串,旧字符串,新字符串) 替换
substring:(字符串,位置,长度) 截字符串 ps:sql中字符串下标从1开始
reverse():反转
len():长度
upper():转大写
lower():转小写

T-Sql

声明变量语法:

1
2
3
declare @变量名 数据类型
set @变量名=
select @变量名=

编程语句:

1
2
begin...end
if...else

视图

  • 1.创建视图

    1
    2
    3
    create view 视图名称
    as
    sql中查询语句
  • 2.使用视图 select * from 视图名

  • 3.查看视图 exec sp_help

  • 4.查看视图内容 exec sp_helptext 视图名

  • 5.修改视图 alter view 视图名 as select * from 表名 [where条件]

  • 6.删除视图 drop view 视图名

  • 7.修改视图 update 视图名 set 字段名=值 [where条件]

存储过程

1
2
3
4
5
6
7
create proc | procedure pro_name
[{@参数数据类型} [=默认值] [output],
{@参数数据类型} [=默认值] [output],
....
]
as
select .....

参考:

sql server 基础教程[温故而知新三]

.NET面试题解析(11)-SQL语言基础及数据库基本原理

轻量级信号量:

CountdownEvent,SemaphoreSlim,ManualResetEventSlim

CountdownEvent

采用信号计数的方式,即定义了最多能够进入关键代码的线程数。并且可以动态改变“信号计数”的大小。

官方示例请参考:CountdownEvent

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp4
{
internal class Program
{
//默认的容纳大小为“硬件线程“数
private static CountdownEvent cde = new CountdownEvent(Environment.ProcessorCount);

private static void Main()
{
int userTaskCount = 5;

//重置信号
cde.Reset(userTaskCount);

for (int i = 0; i < userTaskCount; i++)
{
Task.Factory.StartNew(LoadUser, i);
}

//等待所有任务执行完毕
cde.Wait();
Console.WriteLine(" InitialCount={0}, CurrentCount={1}, IsSet={2}",
cde.InitialCount, cde.CurrentCount, cde.IsSet);
Console.WriteLine("\nUser表数据全部加载完毕!\n");

//加载product需要8个任务
var productTaskCount = 8;

cde.Reset(productTaskCount);
cde.AddCount(2);

Console.WriteLine("After Reset(8), AddCount(2): InitialCount={0}, CurrentCount={1}, IsSet={2}",
cde.InitialCount, cde.CurrentCount, cde.IsSet);

for (int i = 0; i < cde.CurrentCount; i++)
{
Task.Factory.StartNew(LoadProduct, i);
}
//等待所有任务执行完毕
cde.Wait();
Console.WriteLine("\nProduct表数据全部加载完毕!\n");

// Now try waiting with cancellation
CancellationTokenSource cts = new CancellationTokenSource();
cts.Cancel(); // cancels the CancellationTokenSource
try
{
cde.Wait(cts.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("cde.Wait(preCanceledToken) threw OCE, as expected");
}
finally
{
cts.Dispose();
}
// It's good to release a CountdownEvent when you're done with it.
cde.Dispose();

Console.ReadKey();
}

private static void LoadUser(object obj)
{
try
{
Console.WriteLine("当前任务:{0}正在加载User部分数据!", obj);
}
finally
{
cde.Signal();
}
}

private static void LoadProduct(object obj)
{
try
{
Console.WriteLine("当前任务:{0}正在加载Product部分数据!", obj);
}
finally
{
cde.Signal();
}
}
}
}

SemaphoreSlim

对可同时访问资源或资源池的线程数加以限制

官方示例参考:SemaphoreSlim

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp4
{
internal class Program
{
private static SemaphoreSlim semaphore;

// A padding interval to make the output more orderly.
private static int padding;

public static void Main()
{
// Create the semaphore.
semaphore = new SemaphoreSlim(0, 3);
Console.WriteLine("{0} tasks can enter the semaphore.",
semaphore.CurrentCount);
Task[] tasks = new Task[5];

// Create and start five numbered tasks.
for (int i = 0; i <= 4; i++)
{
tasks[i] = Task.Run(() =>
{
// Each task begins by requesting the semaphore.
Console.WriteLine("Task {0} begins and waits for the semaphore.",
Task.CurrentId);

// 信号量: semaphore.Wait()和semaphore.Release()方法范围
semaphore.Wait();

Interlocked.Add(ref padding, 1000);

Console.WriteLine("DateTime:{0},Task {1} enters the semaphore.", DateTime.Now, Task.CurrentId);

// The task just sleeps for 1+ seconds.
Thread.Sleep(1000 + padding);
int id = semaphore.Release();

Console.WriteLine("Task {0} releases the semaphore; previous count: {1}",
Task.CurrentId, id);
});
}

// Wait for half a second, to allow all the tasks to start and block.
Thread.Sleep(500);

// Restore the semaphore count to its maximum value.
Console.Write("Main thread calls Release(3) --> ");
semaphore.Release(3);// 释放3次
Console.WriteLine("{0} tasks can enter the semaphore.",
semaphore.CurrentCount);
// Main thread waits for the tasks to complete.
Task.WaitAll(tasks);

Console.WriteLine("Main thread exits.");

Console.ReadKey();
}
}
}

运行结果:

微信截图_20190306162258.png

ManualResetEventSlim

ManualResetEventSlim是ManualReset的轻量级版本,采用的是”自旋等待“+”内核等待“,也就是说先采用”自旋等待的方式“等待,直到另一个任务调用set方法来释放它。如果迟迟等不到释放,那么任务就会进入基于内核的等待,所以说如果我们知道等待的时间比较短,采用轻量级的版本会具有更好的性能。

官方示例请参考:

ManualResetEventSlim Class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp4
{
internal class Program
{
public static void Main()
{
MRES_SetWaitReset();
MRES_SpinCountWaitHandle();
Console.ReadKey();
}

private static void MRES_SetWaitReset()
{
ManualResetEventSlim mres1 = new ManualResetEventSlim(false,2000); // initialize as unsignaled

Task[] task=new Task[5];

for (var i = 0; i < 5; i++)
{
task[i] = Task.Factory.StartNew(() =>
{
// 等待mres1.Set();
mres1.Wait();
Console.WriteLine("当前时间:{0},任务ID:mres1收到信号开始执行!", DateTime.Now);
});
}
Console.WriteLine("当前时间:{0} 主线程ID:{1} \n",
DateTime.Now,
Thread.CurrentThread.ManagedThreadId);

// 等待两秒
Thread.Sleep(2000);

try
{
// 设置信号,使等待线程开始执行
mres1.Set();
Task.WaitAll(task);
}
finally
{
// 释放
mres1.Dispose();
}
}

// Demonstrates:
// ManualResetEventSlim construction w/ SpinCount
// ManualResetEventSlim.WaitHandle
private static void MRES_SpinCountWaitHandle()
{
// Construct a ManualResetEventSlim with a SpinCount of 1000
// Higher spincount => longer time the MRES will spin-wait before taking lock
ManualResetEventSlim mres1 = new ManualResetEventSlim(false, 1000);
ManualResetEventSlim mres2 = new ManualResetEventSlim(false, 1000);

Task bgTask = Task.Factory.StartNew(() =>
{
// Just wait a little
Thread.Sleep(100);

// Now signal both MRESes
Console.WriteLine("Task signalling both MRESes");
mres1.Set();
mres2.Set();
});

// A common use of MRES.WaitHandle is to use MRES as a participant in
// WaitHandle.WaitAll/WaitAny. Note that accessing MRES.WaitHandle will
// result in the unconditional inflation of the underlying ManualResetEvent.
WaitHandle.WaitAll(new WaitHandle[] { mres1.WaitHandle, mres2.WaitHandle });
Console.WriteLine("WaitHandle.WaitAll(mres1.WaitHandle, mres2.WaitHandle) completed.");

// Clean up
bgTask.Wait();
mres1.Dispose();
mres2.Dispose();
}
}
}

参考:

8天玩转并行开发——第五天 同步机制(下)

屏障简介

System.Threading.Barrier 是同步基元,可以使多个线程(称为“参与者”)分阶段同时处理算法。
屏障表示工作阶段的末尾。 单个参与者到达屏障后将被阻止,直至所有参与者都已达到同一障碍。 所有参与者都已达到屏障后,你可以选择调用阶段后操作。此阶段后操作可由单线程用于执行操作,而所有其他线程仍被阻止。 执行此操作后,所有参与者将不受阻止。

添加和删除参与者

创建 Barrier 实例时,需指定参与者数量。 还可以随时动态添加或删除参与者。 例如,如果其中一个参与者解决了问题的一部分,可以存储结果,停止执行相应线程,并调用 Barrier.RemoveParticipant 以减少屏障中的参与者数量。 当通过调用 Barrier.AddParticipant 添加参与者时,返回值将指定当前阶段的数量,这在初始化新的参与者的工作时很有用。

断开的屏障

如果一个参与者无法到达屏障,则可能发生死锁。若要避免这些死锁,请使用 Barrier.SignalAndWait 方法的重载来指定超时期限和取消标记。 这些重载将返回一个布尔值,每个参与者均可在继续到下一阶段前进行检查。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp4
{
internal class Program
{
//四个task执行
private static Task[] tasks = new Task[4];

private static Barrier _barrier = null;

private static void Main(string[] args)
{
CancellationTokenSource cts = new CancellationTokenSource();

CancellationToken ct = cts.Token;

_barrier = new Barrier(tasks.Length, (i) =>
{
Console.WriteLine("**********************************************************");
Console.WriteLine("\n屏障中当前阶段编号:{0}\n", i.CurrentPhaseNumber);
Console.WriteLine("**********************************************************");
});

for (int j = 0; j < tasks.Length; j++)
{
tasks[j] = Task.Factory.StartNew((obj) =>
{
var single = Convert.ToInt32(obj);

LoadUser(single);

if (!_barrier.SignalAndWait(2000))
{
//抛出异常,取消后面加载的执行
throw new OperationCanceledException(string.Format("我是当前任务{0},我抛出异常了!", single), ct);
}

LoadProduct(single);
_barrier.SignalAndWait();

LoadOrder(single);
_barrier.SignalAndWait();
}, j, ct);
}

//等待所有tasks 4s
Task.WaitAll(tasks, 4000);

try
{
for (int i = 0; i < tasks.Length; i++)
{
if (tasks[i].Status == TaskStatus.Faulted)
{
//获取task中的异常
foreach (var single in tasks[i].Exception.InnerExceptions)
{
Console.WriteLine(single.Message);
}
}
}

_barrier.Dispose();
}
catch (AggregateException e)
{
Console.WriteLine("我是总异常:{0}", e.Message);
}

Console.Read();
}

private static void LoadUser(int num)
{
Console.WriteLine("\n当前任务:{0}正在加载User部分数据!", num);

if (num == 0)
{
//自旋转5s
if (!SpinWait.SpinUntil(() => _barrier.ParticipantsRemaining == 0, 5000))
return;
}

Console.WriteLine("当前任务:{0}正在加载User数据完毕!", num);
}

private static void LoadProduct(int num)
{
Console.WriteLine("当前任务:{0}正在加载Product部分数据!", num);
}

private static void LoadOrder(int num)
{
Console.WriteLine("当前任务:{0}正在加载Order部分数据!", num);
}
}
}

阶段后异常

如果阶段后委托引发异常,则它将包装在 BarrierPostPhaseException 对象中,然后传播到所有参与者。

屏障与 ContinueWhenAll

当线程执行循环中的多个阶段时,屏障特别有用。 如果你的代码仅需一个或多个工作阶段,则应考虑是否配合使用 System.Threading.Tasks.Task 对象与任何类型的隐式联接,其中包括:

  • TaskFactory.ContinueWhenAll

  • Parallel.Invoke

  • Parallel.ForEach

  • Parallel.For

实例代码

msdn官方实例:
统计两个线程使用随机算法重新随机选择字词,分别在同一阶段查找一半解决方案时所需的迭代次数(或阶段数)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
using System;
using System.Text;
using System.Threading;

namespace ConsoleApp4
{
internal class Program
{
private static string[] words1 = new string[] { "brown", "jumps", "the", "fox", "quick" };
private static string[] words2 = new string[] { "dog", "lazy", "the", "over" };
private static string solution = "the quick brown fox jumps over the lazy dog.";

private static bool success = false;

private static Barrier barrier = new Barrier(2, (b) =>
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < words1.Length; i++)
{
sb.Append(words1[i]);
sb.Append(" ");
}
for (int i = 0; i < words2.Length; i++)
{
sb.Append(words2[i]);

if (i < words2.Length - 1)
sb.Append(" ");
}
sb.Append(".");

Console.CursorLeft = 0;
Console.Write("Current phase: {0}", barrier.CurrentPhaseNumber);
if (String.CompareOrdinal(solution, sb.ToString()) == 0)
{
success = true;
Console.WriteLine("\r\nThe solution was found in {0} attempts", barrier.CurrentPhaseNumber);
}
});

private static void Main(string[] args)
{
Thread t1 = new Thread(() => Solve(words1));
Thread t2 = new Thread(() => Solve(words2));
t1.Start();
t2.Start();

// Keep the console window open.
Console.ReadLine();
}

private static void Solve(string[] wordArray)
{
while (success == false)
{
Random random = new Random();
for (int i = wordArray.Length - 1; i > 0; i--)
{
int swapIndex = random.Next(i + 1);
string temp = wordArray[i];
wordArray[i] = wordArray[swapIndex];
wordArray[swapIndex] = temp;
}

barrier.SignalAndWait();
}
}
}
}

简单示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp4
{
internal class Program
{
//四个task执行
private static Task[] tasks = new Task[4];

private static Barrier barrier = null;

private static void Main(string[] args)
{
barrier = new Barrier(tasks.Length, (i) =>
{
Console.WriteLine("**********************************************************");
Console.WriteLine("\n屏障中当前阶段编号:{0}\n", i.CurrentPhaseNumber);
Console.WriteLine("**********************************************************");
});

for (int j = 0; j < tasks.Length; j++)
{
tasks[j] = Task.Factory.StartNew((obj) =>
{
var single = Convert.ToInt32(obj);

LoadUser(single);
barrier.SignalAndWait();

LoadProduct(single);
barrier.SignalAndWait();

LoadOrder(single);
barrier.SignalAndWait();
}, j);
}

Task.WaitAll(tasks);

Console.WriteLine("指定数据库中所有数据已经加载完毕!");

Console.Read();
}

private static void LoadUser(int num)
{
Console.WriteLine("当前任务:{0}正在加载User部分数据!", num);

// 以下代码会造成死锁
/*
if (num == 0)
{
//num=0:表示0号任务
//barrier.ParticipantsRemaining == 0:表示所有task到达屏障才会退出
// SpinWait.SpinUntil: 自旋锁,相当于死循环
SpinWait.SpinUntil(() => barrier.ParticipantsRemaining == 0);
}*/
}

private static void LoadProduct(int num)
{
Console.WriteLine("当前任务:{0}正在加载Product部分数据!", num);
}

private static void LoadOrder(int num)
{
Console.WriteLine("当前任务:{0}正在加载Order部分数据!", num);
}
}
}

参考:

屏障

如何:使用屏障来使并发操作保持同步

8天玩转并行开发——第四天 同步机制(上)

ASP.NET MVC 页面生命周期

整个ASP.NET MVC框架是通过自定义的 HttpModuleHttpHandler 对ASP.NET 进行扩展构建起来的。

  • 自定义的 HttpModule : UrlRoutingModule
  • 自定义的 HttpHandler: MvcHandler

1458860-5d9e4724f9e32ccb.png

190104561291113.png

HttpApplication与HttpModule

HTTP请求由ASP.NET运行时接管之后,HttpRuntime 会利用 HttpApplicationFactory 创建或从 HttpApplication对象池中取出一个HttpApplication对象,同时ASP.NET会根据配置文件来初始化注册的HttpModuleHttpModule在初始化时会订阅HttpApplication中的事件来实现对HTTP请求的处理。

190047183794497.png

ASP.NET MVC的入口在UrlRoutingModule,它订阅了HttpApplication的第7个管道事件PostResolveRequestCahce,并且在HtttpApplication的第7个管道事件处对请求进行了拦截。

UrlRoutingModule

通过在全局Web.Config中注册 System.Web.Routing.UrlRoutingModule,IIS请求处理管道接到请求后,就会加载 UrlRoutingModule类型的Init()方法,第7个管道事件 PostResolveRequestCahce 对请求进行了拦截。

当请求到达 UrlRoutingModule 的时候,UrlRoutingModule 取出请求中的 Controller、Action等RouteData信息,与路由表中的所有规则进行匹配,若匹配,把请求交给 IRouteHandler,即 MVCRouteHandler

1
2
3
4
5
//通过RouteCollection的静态方法GetRouteData获取到封装路由信息的RouteData实例
RouteData routeData = this.RouteCollection.GetRouteData(context);

//再从RouteData中获取MVCRouteHandler
IRouteHandler routeHandler = routeData.RouteHandler;

MVCRouteHadnler

MvcRouteHandler 在HttpApplication的第一个管道事件,使用 MapRoute() 方法注册路由的时候,通过Route类的构造函数把MVCRouteHandler注入到路由中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces)
{
if (routes == null) {
throw new ArgumentNullException("routes");
}
if (url == null) {
throw new ArgumentNullException("url");
}
// MvcRouteHandler
Route route = new Route(url, new MvcRouteHandler()) {
Defaults = new RouteValueDictionary(defaults),
Constraints = new RouteValueDictionary(constraints),
DataTokens = new RouteValueDictionary()
};

if ((namespaces != null) && (namespaces.Length > 0)) {
route.DataTokens["Namespaces"] = namespaces;
}

routes.Add(name, route);

return route;
}

MVCRouteHandler是用来生成实现IHttpHandler接口的MvcHandler

1
2
3
4
5
6
7
namespace System.Web.Routing
{
public interface IRouteHandler
{
IHttpHandler GetHttpHandler(RequestContext requestContext);
}
}

MvcHandler

第7个事件生成了 MvcHandler

第11和第12个事件之间调用了MvcHandlerProcessRequest方法。

通过RouteHandlerGetHttpHandler()方法获取到实现了IHttpHandler接口的MVCHandler

1
2
IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);
context.RemapHandler(httpHandler);

MvcHandler 部分源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class MvcHandler : IHttpAsyncHandler, IHttpHandler, IRequiresSessionState
{
protected internal virtual void ProcessRequest(HttpContextBase httpContext)
{
SecurityUtil.ProcessInApplicationTrust(() =>
{
IController controller;
IControllerFactory factory;
ProcessRequestInit(httpContext, out controller, out factory);//初始化了ControllerFactory
try
{
controller.Execute(RequestContext);
}
finally
{
factory.ReleaseController(controller);
}
});
}

private void ProcessRequestInit(HttpContextBase httpContext, out IController controller, out IControllerFactory factory) {
bool? isRequestValidationEnabled = ValidationUtility.IsValidationEnabled(HttpContext.Current);
if (isRequestValidationEnabled == true) {
ValidationUtility.EnableDynamicValidation(HttpContext.Current);
}
AddVersionHeader(httpContext);
RemoveOptionalRoutingParameters();
string controllerName = RequestContext.RouteData.GetRequiredString("controller");
factory = ControllerBuilder.GetControllerFactory();
controller = factory.CreateController(RequestContext, controllerName);
if (controller == null) {
throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture,MvcResources.ControllerBuilder_FactoryReturnedNull,factory.GetType(),controllerName));
}
}
}

ControllerFactory

通过ControllerBuilder的静态方法GetControllerFactory获取到实现IControllerFactory接口的ControllerFactory。通过ControllerFactory来创建指定名称的控制器,最后将控制器作为out参数传递出去。

ActionInvoker

ActionInvoker实现了IActionInvoker接口:

1
2
3
4
public interface IActionInvoker
{
bool InvokeAction(ControllerContext controllerContext, string actionName);
}

MVC默认的ActionInvoker是ControllerActionInvoker。

ActionInvoker在执行InvokeAction()方法时会需要有关Controller和Action的相关信息,实际上,Controller信息(比如Controller的名称、类型、包含的Action等)被封装在ControllerDescriptor这个类中,Action信息(比如Action的名称、参数、属性、过滤器等)被封装在ActionDescriptor中。ActionDescriptor还提供了一个FindAction()方法,用来找到需要被执行的Action。

Filters

Filter->Action->Filter->Result

在ASP.NET MVC5中有常用的过滤器有5个:IAuthenticationFilterIAuthorizationFilterIActionFilterIResultFilterIExceptionFilter
在ASP.NET MVC中所有的过滤器最终都会被封装为Filter对象,该对象中FilterScope类型的属性Scope和int类型属性Order用于决定过滤器执行的先后顺序,具体规则如下:

  • Order和FilterScope的数值越小,过滤器的执行优先级越高;
  • Order比FilterScope具有更高的优先级,在Order属性值相同时FilterScope才会被考虑
1
2
3
4
5
6
7
8
9
//数值越小,执行优先级越高
public enum FilterScope
{
Action= 30,
Controller= 20,
First= 0,
Global= 10,
Last= 100
}

ActionResult

ActionResult是一个抽象类:

1
2
3
4
public abstract class ActionResult
{
public abstract void ExecuteResult(ControllerContext context);
}

如果ActionResult是非ViewResult,比如JsonResult, ContentResult,这些内容将直接被输送到Response响应流中,显示给客户端;如果是ViewResult,就会进入下一个渲染视图环节。

ViewEngine

默认的有Razor View EngineWeb Form View Engine,实现IViewEngine接口。

IViewEngine接口方法:

  • FindPartialView
  • FindView
  • ReleaseView

参考:

ASP.NET MVC5请求管道和生命周期

ASP.Net请求处理机制初步探索之旅 - Part 5 ASP.Net MVC请求处理流程

ASP.NET MVC请求处理管道生命周期的19个关键环节(13-19)

How ASP.NET MVC Works?

ASP.NET MVC中的ActionFilter是如何执行的?

认识ASP.NET MVC的5种AuthorizationFilter

ASP.NET MVC的View是如何被呈现出来的?[设计篇]

ASP.NET MVC的View是如何呈现出来的[实例篇]

WebForm之ViewState与UpdatePannel详解

ViewState

ViewState简介

1,类似于Dictionary的一种数据结构

ViewState通过String类型的数据作为索引。ViewState对应项中的值可以存储任何类型的值(参数是Object类型),任何类型的值存储到ViewState中都会被装箱为Object类型。

注:ViewState不能存储所有的数据类型,仅支持以下的这几种:
String、Integer、Boolean、Array、ArrayList、Hashtable以及一些自定义类型。

2,ViewState存储在浏览器的页面之中

ViewState的作用域是页面,消耗服务器资源相对较少。

服务器在向浏览器返回html之前,对ViewState中的内容进行了Base64的加密编码。VIEWSTATE适用于同一个页面在不关闭的情况下多次与服务器交互(PostBack)

当用户点击页面中的某个按钮提交表单时,浏览器会将这个_VIEWSTATE的隐藏域也一起提交到服务端;服务器端在解析请求时,会将浏览器提交过来的ViewState进行反序列化后填充到ViewState属性中;再根据业务处理需要,从这个属性中根据索引找到具体的Value值并对其进行操作;操作完成后,再将ViewState进行Base64编码再次返回给浏览器端;

1
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUKLTg4NTU3NzYwOGRkbQwtdx3+TX000hqmVYIcP3+P3zcihvV0deiDjaSgFRQ=" />

ViewState存在的问题

Repeater等控件使用时,存储较大的值,用户请求显示页面的速度会减慢。

1
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUJLTQ2OTExMTk1D2QWAgIBD2QWBAIBDxAPFgYeDkRhdGFWYWx1ZUZpZWxkBQJJRB4NRGF0YVRleHRGaWVsZAUETmFtZR4LXyFEYXRhQm91vZflubPop4Lpn7PooZfmsp/pgJoxMDDljoUn5LqR5Y2X6L+q5L+h6YCa572X5bmz6LSi5a+M5rKf6YCaMTAw5Y6FKuS6keWNl+i/quS/oUCBTExNDczBTExNDczZAIDDw8WAh8FBQUxMTQ3M2RkAgUPDxYCHwUFCzE4Mzg4MDM4MTY1ZGQCBQ8PFgIeC1JlY29yZGNvdW50AvIDZGRkmmJuzxcRIKhlTny8ibiHOR92G/JYSgGwzO69sFxoLV8=" />

禁用ViewState

1,页面级禁用,在Page指令集中添加EnableViewState=”false”。

1
2
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="RepeaterViewState.aspx.cs"
Inherits="WebForm1.RepeaterViewState" EnableViewState="false" %>

注:禁用之后页面依然存在ViewState,但数据量已经减小。

2,控件级禁用ViewState

控件设置一个属性EnableViewState=”false”。

1
2
<asp:Repeater ID="repeaterProducts" runat="server" EnableViewState="false">
</asp:Repeater>

3,全局级禁用ViewState

Web.config的system.web中增加一句配置

1
<pages enableViewState="false" />

4,单独控制禁用与启用

ASP.NET4.0之前,控件的视图只有在 Page 的 ViewState 启用的前提下才可以单独控制。

ASP.NET4.0供了一个叫做 ViewStateMode 的新属性,这个属性可以单独设置控件的视图状态。即使页面的 ViewState 没有启用,控件依然可以启用视图状态。

ViewStateMode 属性有三种取值:

  • Inherit:视图状态从父控件继承;
  • Enabled:即使父控件的视图状态没有启用,也启用该控件的视图状态;
  • Disabled:即使父控件的视图状态启用了,也禁用此控件的视图状态。

UpdatePanel控件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 <form id="form1" runat="server">
<div align="center">
<asp:ScriptManager ID="scriptManager" runat="server">
</asp:ScriptManager>
<asp:UpdatePanel ID="updatePanel" runat="server">
<ContentTemplate>
<asp:TextBox ID="txtNumber1" runat="server"></asp:TextBox>
<asp:DropDownList ID="ddlFunc" runat="server">
<asp:ListItem Value="0">+</asp:ListItem>
<asp:ListItem Value="1">-</asp:ListItem>
<asp:ListItem Value="2">*</asp:ListItem>
<asp:ListItem Value="3">/</asp:ListItem>
</asp:DropDownList>
<asp:TextBox ID="txtNumber2" runat="server"></asp:TextBox>
<asp:Button ID="btnGetResult" runat="server" Text="=" Width="50" OnClick="btnGetResult_Click" />
<asp:Label ID="lblResult" runat="server" Text="" Font-Bold="true"></asp:Label>
</ContentTemplate>
</asp:UpdatePanel>
</div>
</form>

UpdatePanel本质封装了以XmlHttpRequest为核心的一系列方法帮我们将CodeBehind中的同步事件变为了异步操作,并通过DOM更新指定的HTML内容,使得我们可以方便地实现AJAX效果。

参考:

ASP.Net WebForm温故知新学习笔记:二、ViewState与UpdatePanel探秘

禁掉VIEWSTATE之后(一)

Asp.Net 4.0 新特性,输出更纯净的Html代码 ClientIDMode,ViewStateMode等

ASP.NET WebForm页面生命周期

生命周期阶段

1、请求页面

页请求发生在页生命周期开始之前。

2、开始阶段

1,Page类的ProcessRequest方法作为页面生命周期的入口;
2,BuildControlTree:构造页面控件树;
3,设置IsPostBack属性是否第一次请求该页面;

3、初始化页面

页面初始化期间,可以使用页中的控件,并将设置每个控件的UniqueID属性。如果当前请求是回发请求,则回发数据尚未加载,并且控件属性值尚未还原为视图状态中的值。

事件:PreInit-->Init-->InitComplete

** PreInit**

PreInit 是页面生命周期的第一个事件。它检查 IsPostBack 属性并决定页面是否是回发。它设置主题及主版页,创建动态控件,并且获取和设置值配置文件属性值。此事件可通过重载 OnPreInit 方法或者创建一个 Page_PreInit 处理程序进行处置。

** Init**

Init 事件初始化控件属性,并且建立控件树。此事件可通过重载 OnInit 方法或者创建一个 Page_Init处理程序进行处置。

** InitComplete**

InitComplete 事件允许对视图状态的跟踪。所有的控件开启视图状态下的跟踪。

4、加载页面

设置控件属性,使用视图状态和控件状态值。

事件:

1
2
(LoadState-->ProcessPostData-->)PreLoad-->Load-->
(ProcessPostData-->RaiseChangedEvents-->RaisePostBackEvent-->)LoadComplete

** LoadViewState**

允许加载视图状态信息到控件中。

** LoadPostData**

对所有由 \ 标签定义的输入字段的内容进行处理。

** PreLoad**

PreLoad 在回发数据加载在控件中之前发生。此事件可以通过重载 OnPreLoad 方法或者创建一个 Pre_Load 处理程序进行处置。

** Load **

Load 事件被页面最先引发,然后递归地引发所有子控件。控件树中的控件就被创建好了。此事件可通过重载 OnLoad 方法或者创建一个 Page_Load 处理程序来进行处置。

5、验证

在验证期间,将调用所有验证程序控件的Validate方法,此方法将设置各个验证程序控件和页的IsValid属性为 true。

** LoadComplete**

加载进程完成,控件事件处理程序运行,页面验证发生。此事件可通过重载 OnLoad 方法或者创建一个 Page_LoadComplete 处理程序来进行处置。

6、回发事件处理

如果请求是回发请求,则将调用所有事件处理程序。

7、呈现页面

页面的视图状态和所有控件被保存。页面为每一个控件调用显示方法,并且呈现的输出被写入页面响应属性中的 OutputStream 类。

** PreRender**

PreRender 事件就在输出显示之前发生。通过处理此事件,页面和控件可以在输出显示之前执行任何更新。

** PreRenderComplete**

当 PreRender 事件为所有子控件被循环引发,此事件确保了显示前阶段的完成

** SaveStateComplete**

页面控件状态被保存。个性化、控件状态以及视图状态信息被保存。

8、卸载页面

显示过的页面被送达客户端以及页面属性,例如响应和请求,被卸载并全部清除完毕。

参考:

用三张图片详解Asp.Net 全生命周期

ASP.Net请求处理机制初步探索之旅 - Part 4 WebForm页面生命周期

ASP.NET 页面生命周期

Asp.Net WebForm生命周期的详解

2012071209470854.png

IIS 6.X和IIS7经典模式,采用aspnet_isapi.dll来响应请求;
IIS7集成模式则执行单独的管道;

注:ASP.NET WebForm和ASP.NET MVC 同用一套请求处理管道。

创建HttpApplication

创建 HttpApplication(即w3wp.exe)对象的时候,先去对象池中查找,没有则创建(编译Global.asax,创建HttpApplication对象),找到则直接返回对象。

HttpApplication的工作包括:

● 初始化的时候加载全部的HttpModule
● 接收请求
● 在不同阶段引发不同的事件,使得HttpModule通过订阅事件的方式加入到请求的处理过程中
● 在一个特定阶段获取一个IHttpHandler实例,最终将请求交给具体的IHttpHandler来实现

HttpApplication 创建之后,则是ASP.NET请求处理管道。

HttpModules & HttpHandlers

** HttpModules**

所有的HttpModules都实现了IHttpModule接口,可以实现 IHttpModule 接口自定义 HttpModule

** HttpHandlers**

当某个请求与一个规则匹配后,ASP.NET会调用匹配的HttpHandlerFactory的GetHandler方法来获取一个HttpHandler实例, 最后由一个HttpHandler实例来处理当前请求,生成响应内容

所有的 HttpHandlers 都实现了 IHttpHandler 接口,可以实现 IHttpHandler 接口自定义 HttpHandler

如图:

190047183794497.png

Asp.Net请求处理事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
1、BeginRequest:HTTP管道开始处理请求时,会触发BeginRequest事件

2、AuthenticateRequest:安全模块对请求进行身份验证时触发该事件

3、PostAuthenticateRequest:安全模块对请求进行身份验证后触发该事件

4、AuthorizeRequest:安全模块对请求进程授权时触发该事件

5、PostAuthorizeRequest:安全模块对请求进程授权后触发该事件

6、ResolveRequestCache:缓存模块利用缓存直接对请求进程响应时触发该事件

7、PostResolveRequestCache:缓存模块利用缓存直接对请求进程响应后触发该事件

8、PostMapRequestHandler:对于访问不同的资源类型,ASP.NET具有不同的HttpHandler对其进程处理。
对于每个请求,ASP.NET会根据扩展名选择匹配相应的HttpHandler类型,成功匹配后触发该事件

9、AcquireRequestState:状态管理模块获取基于当前请求相应的状态(比如SessionState)时触发该事件

10、PostAcquireRequestState:状态管理模块获取基于当前请求相应的状态(比如SessionState)后触发该事件

11、PreRequestHandlerExecute:在实行HttpHandler前触发该事件

12、PostRequestHandlerExecute:在实行HttpHandler后触发该事件

13、ReleaseRequestState:状态管理模块释放基于当前请求相应的状态时触发该事件

14、PostReleaseRequestState:状态管理模块释放基于当前请求相应的状态后触发该事件

15、UpdateRequestCache:缓存模块将HttpHandler处理请求得到的相应保存到输出缓存时触发该事件

16、PostUpdateRequestCache:缓存模块将HttpHandler处理请求得到的相应保存到输出缓存后触发该事件

17、LogRequest:为当前请求进程日志记录时触发该事件

18、PostLogReques:为当前请求进程日志记录后触发该事件

19、EndRequest:整个请求处理完成后触发该事件

20、PreSendRequestHeaders:.Net4.0新增,可以根据发送的Header设置一些参数,
例如,设置一个特殊的Header通知浏览器启用压缩来提高网络传输速度

21、PreSendRequestContent:.Net4.0新增,恰好在 ASP.NET 向客户端发送内容之前发生。
例如,在此事件压缩输出到浏览器的流。

webform 和 mvc 共用一套管道,在第7个事件 PostResolveRequestCache 会根据Url去路由表匹配所有的路由规则,
如果匹配到了路由,则证明请求是MVC程序,反之,则是WebForm。

WebForm 管道事件摘要

1,第八个事件 PostMapRequestHandler 创建Page类对象并转换为IHttpHandler接口。

在这个事件中,对于访问不同的资源类型,ASP.NET具有不同的HttpHandler对其进程处理。对于每个请求,ASP.NET会通过扩展名选择匹配相应的HttpHandler类型,成功匹配后,该实现被触发。因此,如果请求的扩展名是.aspx,便会生成Page类对象,而Page类对象是实现了IHttpHandler接口的。

2,在第九个到第十事件之间根据SessionId获取Session

第九到第十个事件是:AcquireRequestStatePostAcquireRequestState

这期间首先会接收到浏览器发过来的SessionId,然后先会将IHttpHandler接口尝试转换为IRequiresSessionState接口,如果转换成功,ASP.NET会根据这个SessionId到服务器的Session池中去查找所对应的Session对象,并将这个Session对象赋值到HttpContext对象的Session属性。如果尝试转换为IRequiresSessionState接口不成功,则不加载Session。

3,在第十一个事件与第十二个事件之间执行页面生命周期

第十一和第十二个事件是:PreRequestHandlerExecutePostRequestHandlerExecute。在这两个事件之间,ASP.NET最终通过请求资源类型相对应的HttpHandler实现对请求的处理,其实现方式是调用在第八个事件创建的页面对象的ProcessRequest方法。

051837105625665.jpg

FrameworkInitialize() 这个方法内部就开始打造WebForm的页面控件树,在其中调用了ProcessRequestMain方法,在这个方法里面就执行了整个ASP.NET WebFom页面生命周期。

MVC 管道事件摘要

在ASP.NET MVC中,最核心的当属“路由系统”,而路由系统的核心则源于一个强大的 System.Web.Routing.dll 组件。

在这个System.Web.Routing.dll中,有一个最重要的类叫做UrlRoutingModule , ASP.NET MVC的入口在 UrlRoutingModule,它是一个实现了IHttpModule 接口的类,订阅了 HttpApplication 的第7个管道事件 PostResolveRequestCahce 对请求进行了拦截。

1,IIS网站的配置可以分为两个块:全局 Web.config 和本站 Web.config。Asp.Net Routing属于全局性的,所以它配置在全局Web.Config 中,我们可以在如下路径中找到:“$\Windows\Microsoft.NET\Framework\版本号\Config\Web.config“

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="utf-8"?>
<!-- the root web configuration file -->
<configuration>
<system.web>
<httpModules>
<add name="UrlRoutingModule-4.0" type="System.Web.Routing.UrlRoutingModule" />
</httpModules>
</system.web>
</configuration>

2,通过在全局Web.Config中注册 System.Web.Routing.UrlRoutingModule,IIS请求处理管道接到请求后,就会加载 UrlRoutingModule类型的Init()方法,第7个管道事件 PostResolveRequestCahce 对请求进行了拦截。

3,在第七个事件中创建实现了IHttpHandler接口的MvcHandler

第七个事件:PostResolveRequestCache

当请求到达 UrlRoutingModule 的时候,UrlRoutingModule 取出请求中的 Controller、Action等RouteData信息,与路由表中的所有规则进行匹配,若匹配,把请求交给 IRouteHandler,即 MVCRouteHandler。我们可以看下UrlRoutingModule 的源码来看看,以下是几句核心的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public virtual void PostResolveRequestCache(HttpContextBase context)
{
// 通过RouteCollection的静态方法GetRouteData获取到封装路由信息的RouteData实例
RouteData routeData = this.RouteCollection.GetRouteData(context);
if (routeData != null)
{
// 再从RouteData中获取MVCRouteHandler
IRouteHandler routeHandler = routeData.RouteHandler;
......
if (!(routeHandler is StopRoutingHandler))
{
......
// 调用 IRouteHandler.GetHttpHandler(),获取的IHttpHandler 类型实例,它是由 IRouteHandler.GetHttpHandler获取的
IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);
......
// 合适条件下,把之前将获取的IHttpHandler 类型实例 映射到IIS HTTP处理管道中
context.RemapHandler(httpHandler);
}
}
}

MVCRouteHandler 的作用是用来生成实现 IHttpHandler 接口的 MvcHandler

1
2
3
4
5
6
7
namespace System.Web.Routing
{
public interface IRouteHandler
{
IHttpHandler GetHttpHandler(RequestContext requestContext);
}
}

获取 MvcRouteHadnler :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 继承HttpApplication
public class MvcApplication : System.Web.HttpApplication
{
// 第一个管道事件
protected void Application_Start()
{
......
//注册路由
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}

public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
// MapRoute位于System.Web.Mvc.RouteCollectionExtensions
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}

// System.Web.Mvc.RouteCollectionExtensions 扩展方法
public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces)
{
......
   // 通过Route类的构造函数把 MvcRouteHandler 注入到路由中
Route route = new Route(url, new MvcRouteHandler()) {
Defaults = new RouteValueDictionary(defaults),
Constraints = new RouteValueDictionary(constraints),
DataTokens = new RouteValueDictionary()
};
......
return route;
}

在第11和第12个事件之间调用了 MvcHandlerProcessRequest 方法处理ASP.NET MVC。

第十一和第十二个事件是:PreRequestHandlerExecutePostRequestHandlerExecute

获取 MvcHandler:

1
2
3
4
5
6
7
8
9
IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);
context.RemapHandler(httpHandler);

// MvcHandlerd的实现
protected virtual IHttpHandler GetHttpHandler(RequestContext requestContext)
{
  requestContext.HttpContext.SetSessionStateBehavior(GetSessionStateBehavior(requestContext));
return new MvcHandler(requestContext);
}

如图:

190104512548523.png

参考:

再谈IIS与ASP.NET管道

【深入ASP.NET原理系列】–ASP.NET请求管道、应用程序生命周期、整体运行机制

ASP.NET MVC请求处理管道生命周期的19个关键环节(1-6)

ASP.NET MVC请求处理管道生命周期的19个关键环节(7-12)

ASP.Net请求处理机制初步探索之旅 - Part 3 管道

ASP.NET在IIS运行模式

IIS6 处理模式

  IIS 6引入了Application Pool。Application Pool就是一个application的容器,在IIS 6中,我们可以创建若干Application Pool,在创建Web Application的时候,我们为它指定一个既定的application pool。在运行的时候,一个Application对应一个Worker Process:w3wp.exe。也就是说,和前一个版本的IIS不同的是,对于IIS 6来说,同一台机器上可以同时运行多个Worker Process,每个Worker Process中的每个Application domain对应一个Application。这样,Application之间不但能提供Application Domain级别的隔离,你也可以将不同的Application置于不同的Application Pool中,从而基于Process级别的隔离。对于Host 一些重要的Application来说,这样的方式可以提供很好的Reliability。

注:为了避免用户应用程序访问或者修改关键的操作系统数据,windows提供了两种处理器访问模式:用户模式(User Mode)和内核模式(Kernel Mode)。一般地,用户程序运行在User mode下,而操作系统代码运行在Kernel Mode下。Kernel Mode的代码允许访问所有系统内存和所有CPU指令。

  在User Mode下,http.sys接收到一个基于aspx的http request,然后它会根据IIS中的Metabase查看该基于该Request的Application属于哪个Application Pool,如果该Application Pool不存在,则创建之。否则直接将request发到对应Application Pool的Queue中。每个Application Pool对应着一个Worker Process:w3wp.exe,是运行在User Mode下的。在IIS Metabase中维护着Application Pool和worker process的Mapping。WAS(Web Administrative service)根据这样一个mapping,将存在于某个Application Pool Queue的request 传递到对应的worker process(如果没有,就创建这样一个进程)。在worker process初始化的时候,加载ASP.NET ISAPI,ASP.NET ISAPI进而加载CLR。最后的流程就和IIS 5.x一样了:通过AppManagerAppDomainFactory的Create方法为Application创建一个Application Domain;通过ISAPIRuntime的ProcessRequest处理Request,进而将流程进入到ASP.NET Http Runtime Pipeline。

IIS6-1.jpg

  • HTTP.SYS:(Kernel)的一个组件,它负责侦听(Listen)来自于外部的HTTP请求,根据请求的URL将其转发给相应的应用程序池 (Application Pool)。当此HTTP请求处理完成时,它又负责将处理结果发送出去.为了提供更好的性能,HTTP.SYS内部建立了一个缓冲区,将最近的HTTP请求处理结果保存起来。
  • Application Pool:IIS总会保持一个单独的工作进程:应用程序池。所有的处理都发生在这个进程里,包括ISAPI dll的执行。对于IIS6而言,应用程序池是一个重大的改进,因为它们允许以更小的粒度控制一个指定进程的执行。你可以为每一个虚拟目录或者整个Web 站点配置应用程序池,这可以使你很容易的把每一个应用程序隔离到各自的进程里,这样就可以把它与运行在同一台机器上其他程序完全隔离。从Web处理的角度看,如果一个进程死掉,至少它不会影响到其它的进程。当应用程序池接收到HTTP请求后,交由在此应用程序池中运行的工作者进程Worker Process: w3wp.exe来处理此HTTP请求。
  • Worker Process: 当工作者进程接收到请求后,首先根据后缀找到并加载对应的ISAPI扩展 (如:aspx 对应的映射是aspnet_isapi.dll),工作者进程加载完aspnet_isapi.dll后,由aspnet_isapi.dll负责加载 ASP.NET应用程序的运行环境即CLR (.NET Runtime)。 Worker Process运行在非托管环境,而.NET中的对象则运行在托管环境之上(CLR),它们之间的桥梁就是ISAPI扩展。
  • WAS(Web Admin Service):这是一个监控程序,它一方面可以存取放在InetInfo元数据库(Metabase)中的各种信息,另一方面也负责监控应用程序池(Application Pool)中的工作者进程的工作状态况,必要时它会关闭一个老的工作者进程并创建一个新的取而代之。

IIS6.X 运行时

在worker process(w3wp.ext)初始化的时候,加载ASP.NET ISAPI,ASP.NET ISAPI进而加载CLR。
ASP.NET ISAPI运行在一个非托管环境之中,ASP.NET ISAPI通过调用System.Web.Hosting.ISAPIRuntime Instance的ProcessRequest方法,进而从非托管的环境进入了托管的环境。

iis_aspnet_process_mode_02_01.JPG

具体请参考:ASP.NET Process Model之二:ASP.NET Http Runtime Pipeline[上篇]

最后,通过 AppManagerAppDomainFactory 的Create方法为 Application 创建一个 Application Domain;通过ISAPIRuntimeProcessRequest 处理 Request,进而将流程进入到 ASP.NET Http Runtime Pipeline

如下图:

2012071209470854.png

HttpContext(Request,Response)→ HttpRuntime→HttpApplicationFactory→HttpApplication→ HttpModule→HttpHandler→EndRequest

iis_aspnet_process_mode_02_02.JPG

IIS7 处理模式

IIS 7.0支持经典模式和集成模式。IIS6及IIS7的经典模式需要aspnet_isapi.dll来处理,而IIS7集成模式不需要aspnet_isapi.dll来处理,而可以直接根据文件扩展名找到相应的处理程序接口。例如aspx的处理程序是System.Web.UI.PageHandlerFactory类型。

IIS 7.0对请求的监听和分发机制上又进行了革新性的改进,主要体现在对于Windows进程激活服务(Windows Process Activation Service,WAS)的引入,将原来(IIS 6.0)W3SVC承载的部分功能分流给了WAS。具体来说,通过上面的介绍,我们知道对于IIS 6.0来说,W3SVC主要承载着三大功能:

  • HTTP请求接收:接收HTTP.SYS监听到的HTTP请求;
  • 配置管理:从元数据库(Metabase)中加载配置信息对相关组件进行配置;
  • 进程管理:创建、回收、监控工作进程。

在IIS 7.0,后两组功能被移入WAS中,接收HTTP请求的任务依然落在W3SVC头上。

经典模式

经典模式与IIS 6或者之前版本保持兼容的一种模式。

利用文件扩展名,可以判断使用哪个ISAPI处理程序。例如,可以将扩展名为.aspx 和.ascx的文件映射到aspnet_isapi.dll;并且将扩展名为.asp的文件映射到asp.dll,这样就可以处理传统的ASP页面。

04103814-376a25a1abe84619a45a90e3882ac641.jpg

集成模式

04103815-d7ef2284320f485d9f048b507062cc4d.png

1、当客户端浏览器开始 HTTP 请求一个WEB 服务器的资源时,HTTP.sys 拦截到这个请求。

2、HTTP.sys 联系 WAS 获取配置信息。

3、WAS 向配置存储中心(applicationHost.config)请求配置信息。

4、WWW 服务接收到配置信息,配置信息指类似应用程序池配置信息,站点配置信息等等。

5、WWW 服务使用配置信息去配置 HTTP.sys 处理策略。

6、WAS为请求创建一个进程(如果不存在的话)

7、工作者进程处理请求并对HTTP.sys做出响应.

8、客户端接受到处理结果信息。

  集成模式中,asp.net不再像IIS6一样只限定于aspnet_isapi.dll中,而是被解放出来,从IIS接收到HTTP请求开始,即进入asp.net的控制范围,asp.net可以存在于一个请求在IIS中各个处理阶段。允许我们将ASP.NET更好地与IIS集成,甚至允许我们在ASP.NET中编写一些功能(例如Module)来改变IIS的行为(扩 展)。集成的好处是,不再通过ISAPI的方式,提高了速度和稳定性。至于扩展,则可以使得我们对于IIS,以及其他类型的请求有更多的控制。(例如,我们希望静态网页也具备一些特殊的行为)。

IIS 7.0集成模式下的优点:

  • 允许我们通过本地代码(Native Code)和托管代码(Managed Code)两种方式定义IIS Module,这些IIS Module注册到IIS中形成一个通用的请求处理管道。由这些IIS Module组成的这个管道能够处理所有的请求,不论请求基于怎样的资源类型。比如,可以将FormsAuthenticationModule提供的Forms认证应用到基于.aspx,CGI和静态文件的请求。
  • 将ASP.NET提供的一些强大的功能应用到原来难以企及的地方,比如将ASP.NET的URL重写功能置于身份验证之前;
  • 采用相同的方式去实现、配置、检测和支持一些服务器特性(Feature),比如Module、Handler映射、错误定制配置(Custom Error Configuration)等。

参考:

ASP.NET Process Model之一:IIS 和 ASP.NET ISAPI

用三张图片详解Asp.Net 全生命周期

IIS与ASP.NET Http Runtime Pipeline

瀚海拾贝(一)HTTP协议/IIS 原理及ASP.NET运行机制浅析【图解】

ASP.NET是如何在IIS下工作的

使用程序集编程

程序集是 .NET Framework 的构造块;它们构成了部署、版本控制、重复使用、激活范围和安全权限的基本单元。 程序集向公共语言运行时提供了解类型实现所需要的信息。 程序集是为协同工作而生成的类型和资源的集合,这些类型和资源构成了一个逻辑功能单元。 对于运行时,类型不存在于程序集上下文之外。

程序集名称

程序集的名称存储在元数据中,它对程序集的范围及应用程序对程序集的使用有重要影响。 强名称程序集有一个完全限定的名称,由程序集的名称、区域性、公钥及版本号组成。 该名称通常称为显示名称,对于加载的程序集,可通过使用 FullName 属性来获取它。

在 .NET Framework 2.0 版中,向程序集标识添加了处理器体系结构,从而允许使用特定于处理器的程序集版本。

例如:

完全限定名表明 myTypes 程序集的强名称具有公钥标记、区域性值为美国英语、版本号为 1.0.1234.0。 它的处理器体系结构为“msil”,表示程序集将以实时 (JIT) 方式编译为 32 位代码或 64 位代码(具体取决于操作系统和处理器)。

1
myTypes, Version=1.0.1234.0, Culture=en-US, PublicKeyToken=b77a5c561934e089c, ProcessorArchitecture=msil  

请求程序集中的类型的代码必须使用完全限定的程序集名称。 这称为完全限定绑定。 在 .NET Framework 中引用程序集时不允许使用部分绑定,因为它只指定一个程序集名称。

对组成 .NET Framework 的程序集的所有程序集引用也必须包含程序集的完全限定名。对于 .NET Framework 程序集,区域性值始终不是特定的。

1
System.data, version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089  

绑定到程序集时,运行时不区分程序集名称的大小写,但会保留程序集名称中使用的大小写。

如果将强名称程序集置于全局程序集缓存中,则程序集的文件名必须与程序集名称相匹配(不包括文件扩展名,如 .exe 或 .dll)。 例如,如果程序集的文件名为 myAssembly.dll,则程序集名称必须为 myAssembly。 只有在根应用程序目录中部署的专用程序集的程序集名称可以不同于文件名。

确定程序集完全限定的名称

1,在全局程序集缓存中查找一个程序集的完全限定名,请使用全局程序集缓存工具 (Gacutil.exe)。

2,不在全局程序集缓存中的程序集,可以通过多种方式获取完全限定的程序集名称:可使用代码将信息输出到控制台或变量,也可使用 Ildasm.exe(IL 反汇编程序)检查包含完全限定名的程序集元数据。

如果应用程序已加载程序集,则可检索 Assembly.FullName 属性的值在以获取完全限定名。

1
2
Type t = typeof(System.Data.DataSet);
string s = t.Assembly.FullName.ToString();

如果知道程序集的文件系统路径,则可调用静态AssemblyName.GetAssemblyName 方法获取完全限定的程序集名称。

1
Console.WriteLine(AssemblyName.GetAssemblyName(@".\UtilityLibrary.dll"));

设置程序集特性

程序集特性是提供程序集相关信息的值。 特性分为以下几组信息:

  • 程序集标识特性。
  • 信息性特性。
  • 程序集清单特性。
  • 强名称特性。

参考:设置程序集特性

重温.NET下Assembly的加载过程

AppDomain介绍

为了保证代码的键壮性CLR希望不同服务功能的代码之间相互隔离,这种隔离可以通过创建多个进程来实现,但操作系统中创建进程是即耗时又耗费资源的一件事,所以在CLR中引入了AppDomain的概念,AppDomain 主要是用来实现同一进程中的各 AppDomain 之间的隔离。

AppDomain 是.Net 平台里一个很重要的特性,在.Net以前,每个程序是 封装 在不同的进程中的,这样导致的结果就造就占用资源大,可复用性低等缺点。而 AppDomain 在同一个进程内划分出多个 ,一个进程可以运行多个应用,提高了资源的复用性,数据通信等。

CLR在启动的时候会创建系统域(System Domain)共享域(Shared Domain)默认域(Default Domain),系统域与共享域对于用户是不可见的,默认域也可以说是当前域,它承载了当前应用程序的各类信息(堆栈),所以,我们的一切操作都是在这个默认域上进行。插件式 开发很大程度上就是依靠 AppDomain 来进行。

应用程序域具有以下特点:

  • 必须先将程序集加载到应用程序域中,然后才能执行该程序集。
  • 一个应用程序域中的错误不会影响在另一个应用程序域中运行的其他代码。
  • 能够在不停止整个进程的情况下停止单个应用程序并卸载代码。不能卸载单独的程序集或类型,只能卸载整个应用程序域。
    阅读全文 »

程序集

程序集内容

参考:程序集内容

通常,静态程序集可能由以下四个元素组成:

  • 1,程序集清单,包含程序集元数据。
  • 2,类型元数据。
  • 3,实现这些类型的 Microsoft 中间语言 (MSIL) 代码。
  • 4,资源集。

只有程序集清单是必需的,但也需要类型或资源来向程序集提供任何有意义的功能。

单文件程序集

assemblyover1.gif

多文件程序集

注意:Visual Studio IDE 只能用于创建单文件程序集。

将一个程序集的元素包含在几个文件中。 这些文件可以是应用程序所需的编译代码 (.netmodule)、资源(例如,.bmp 或 .jpg 文件)或其他文件的模块。

.NET Framework 只在文件被引用时下载该文件;通过将很少引用的代码保留在独立于应用程序的文件中来优化代码下载。

构成多文件程序集的那些文件实际上并非由文件系统来链接。 它们而是通过程序集清单进行链接,公共语言运行时将这些文件作为一个单元来管理。

assemblyover2.gif

在此插图中,所有三个文件均属于一个程序集,如 MyAssembly.dll 所包含的程序集清单文件中所述。 对于该文件系统,这三个文件是三个独立的文件。 请注意,文件 Util.netmodule 被编译为一个模块,因为它不包含任何程序集信息。 创建程序集之后,已将该程序集清单添加到指示程序集与 Util.netmodule 和 Graphic.bmp 之间关系的 MyAssembly.dll 中。

程序集清单

参考:程序集清单

每一程序集,无论是静态的还是动态的,均包含描述该程序集中各元素彼此如何关联的数据集合。 程序集清单就包含这些程序集元数据。 程序集清单包含指定该程序集的版本要求和安全标识所需的所有元数据,以及定义该程序集的范围和解析对资源和类的引用所需的全部元数据。 程序集清单可以存储在具有 Microsoft 中间语言 (MSIL) 代码的 PE 文件(.exe 或 .dll)中,也可存储在只包含程序集清单信息的独立 PE 文件中。

对于有一个关联文件的程序集,该清单将被合并到 PE 文件中以构成单文件程序集。

每一程序集的清单均执行以下功能:

  • 枚举构成该程序集的文件。
  • 控制对该程序集的类型和资源的引用,如何映射到包含其声明和实现的文件。
  • 枚举该程序集所依赖的其他程序集。
  • 在程序集使用者和程序集实现详细信息的使用者之间提供一定程度的间接性。
  • 呈现程序集自述。

程序集清单内容

信息 描述
程序集名称 指定程序集名称的文本字符串。
版本号 主版本号和次版本号,以及修订号和生成号。 公共语言运行时使用这些编号来强制实施版本策略。
culture 有关该程序集支持的区域性或语言的信息。此信息只应用于将一个程序集指定为包含特定区域性或特定语言信息的附属程序集。
强名称信息 如果已经为程序集提供了一个强名称,则为来自发行者的公钥。
程序集中所有文件的列表 在程序集中包含的每一文件的散列及文件名。 请注意,构成程序集的所有文件所在的目录
必须是包含该程序集清单的文件所在的目录。
类型引用信息 运行时用来将类型引用映射到包含其声明和实现的文件的信息。 该信息用于从程序集导出的类型。
有关被引用程序集的信息 该程序集静态引用的其他程序集的列表。 如果依赖的程序集具有强名称,
则每一引用均包括该依赖程序集的名称、程序集元数据(版本、区域性、操作系统等)和公钥。

全局程序集缓存

参考:全局程序集缓存

安装了公共语言运行时的每台计算机均具有计算机范围的代码缓存,称为全局程序集缓存。 全局程序集缓存中存储专门指定给由计算机中若干应用程序共享的程序集。

只能在需要时才通过将程序集安装到全局程序集缓存中来共享程序集。 一般原则是:程序集依赖项保持专用,并将程序集放在应用程序目录中,除非明确要求共享该程序集。 另外,无需为了使 COM 互操作或非托管代码可以访问程序集而将程序集安装到全局程序集缓存。

将组成应用程序的某个程序集置于全局程序集缓存中之后,无法再通过使用 xcopy 命令复制应用程序目录来复制或安装应用程序。 必须同时移动全局程序集缓存中的程序集。

可以通过两种方法将程序集部署到全局程序集缓存:

从 .NET Framework 4 开始,全局程序集缓存的默认位置为 %windir%\Microsoft.NET\assembly。 在 .NET Framework 的早期版本中,默认位置为 %windir%\assembly。

建议只允许具有“管理员”权限的用户从全局程序集缓存中删除文件。

部署在全局程序集缓存中的程序集必须使用强名称。 将程序集添加到全局程序集缓存时,可对组成该程序集的所有文件执行完整性检查。 缓存通过执行这些完整性检查来确保该程序集未被篡改,例如,文件已更改,但清单没有反映出此更改。

具有强名称的程序集

参考:具有强名称的程序集

强命名一个程序集可为程序集创建唯一的标识,并且可以防止程序集冲突。

强名称程序集通过使用私钥以及程序集本身生成,此私钥对应于与该程序集一起分发的公钥。 程序集包括程序集清单,此清单包含所有组成该程序集的文件的名称和哈希。 具有相同强名称的程序集应该完全相同。

在创建强名称程序集时,它包含程序集的简单文本名称、版本号、可选区域性信息、数字签名,以及对应于用于签名的私钥的公钥。

不要依赖于通过强名称实现安全性。 它们仅提供唯一的标识。

强命名程序集作用:

  • 你希望启用强名称程序集将引用的程序集,或希望允许其他强名称程序集 friend 访问你的程序集。
  • 应用程序需要访问同一程序集的各种版本。 这意味着你需要在同一应用程序域中并排加载某程序集的不同版本,且各版本互不冲突。 例如,如果在具有相同简单名称的程序集中存在 API 的不同扩展,强命名将为该程序集的每个版本提供唯一标识。
  • 你不希望程序集的使用对应用程序性能产生负面影响,所以你想要非特定于域的程序集。 这就要求进行强命名,因为非特定于域的程序集必须安装在全局程序集缓存中。
  • 如果你希望通过应用发布服务器策略来集中应用程序的服务,则意味着程序集必须安装在全局程序集缓存中。

程序集版本控制

使用公共语言运行时的程序集的所有版本控制都在程序集级别上进行。 一个程序集的特定版本和依赖程序集的版本在该程序集的清单中记录下来。 除非被配置文件(应用程序配置文件、发行者策略文件和计算机的管理员配置文件)中的显式版本策略重写,否则运行时的默认版本策略是,应用程序只与它们生成和测试时所用的程序集版本一起运行。

仅对具有强名称的程序集进行版本控制。

每一程序集都用两种截然不同的方法来表示版本信息:

  • 程序集的版本号,该版本号与程序集名称及区域性信息都是程序集标识的组成部分。 该号码将由运行时用来强制实施版本策略,它在运行时的类型解析进程中起着重要的作用。
  • 信息性版本,这是一个字符串,表示仅为提醒的目的而包括的附加版本信息。

程序集版本号

每一程序集都有一个版本号作为其标识的一部分。 因此,如果两个程序集具有不同的版本号,运行时就会将它们视作完全不同的程序集。

<主版本>.<次版本>.<生成号>.<修订版本>

例如,版本 1.5.1254.0 中的 1 表示主版本,5 表示次版本,1254 表示生成号,而 0 则表示修订号。

版本号与其他标识信息(包括程序集名称和公钥,以及与该应用程序所连接的其他程序集的关系和标识有关的信息)一起存储在程序集清单中。

在生成程序集时,开发工具将把每一个被引用程序集的依赖项信息记录在程序集清单中。 运行时将这些版本号与管理员、应用程序或发行者设置的配置信息结合使用,以加载被引用程序集的正确版本。

为进行版本控制,运行时会区分常规程序集和具有强名称的程序集。 只对具有强名称的程序集执行版本检查。