程序猿修仙之路--数据结构之你是否真的懂数组?
数据结构
但凡IT江湖侠士,算法与数据结构为必修之课。早有前辈已经明确指出:程序=算法+数据结构 。要想在之后的江湖历练中通关,数据结构必不可少。数据结构与算法相辅相成,亦是阴阳互补之法。
开篇
说道数组,几乎每个IT江湖人士都不陌生,甚至过半人还会很自信觉的它很简单。 的确,在菜菜所知道的编程语言中几乎都会有数组的影子。不过它不仅仅是一种基础的数据类型,更是一种基础的数据结构。如果你觉的对数组足够了解,那能不能回答一下:
数组的本质定义?
数组的内存结构?
数组有什么优势?
数组有什么劣势?
数组的应用场景?
数组为什么大部分都从0开始编号?
数组能否用其他容器来代替,例如c#中的List<T>?
定义
所谓数组,是相同的元素序列。数组是在程序设计中,为了处理方便,把具有相同类型的若干元素按无序的形式组织起来的一种形式。
——百科
正如以上所述,数组在应用上属于数据的容器。不过我还是要补充两点:
1. 数组在数据结构范畴属于一种线性结构,也就是只有前置节点和后续节点的数据结构,除数组之外,像我们平时所用的队列,栈,链表等也都属于线性结构。
有线性结构当然就有非线性结构,比如之后我们要介绍的二叉树,图 等等,这里不再展开~~~
2. 数组元素在内存分配上是连续的。这一点对于数组这种数据结构来说非常重要,甚至可以说是它最大的“杀手锏”。下边会有更详细的介绍。
优势和劣势
优势
我相信所有人在使用数组的时候都知道数组可以按照下标来访问,例如 array[1] 。作为一种最基础的数据结构是什么使数组具有这样的随机访问方式呢?天性聪慧的你可能已经想到了:内存连续+相同数据类型。
现在我们抽象一下数据在内存上分配的情景。
1. 说到数组按下标访问,不得不说一下大多数人的一个“误解”:数组适合查找元素。为什么说是误解呢,是因为这种说法不够准确,准确的说数组适合按下标来查找元素,而且按照下标查找元素的时间复杂度是O(1)。为什么呢?我们知道要访问数组的元素需要知道元素在内存中对应的内存地址,而数组指向的内存的地址为首元素的地址,即:array[0]。由于数组的每个元素都是相同的类型,每个类型占用的字节数系统是知道的,所以要想访问一个数组的元素,按照下标查找可以抽象为:
array[n]=array[0]+size*n
以上是元素地址的运算,其中size为每个元素的大小,如果为int类型数据,那size就为4个字节。其实确切的说,n的本质是一个离首元素的偏移量,所以array[n]就是距离首元素n个偏移量的元素,因此计算array[n]的内存地址只需以上公式。
论证一下,如果下标从1开始计算,那array[n]的内存地址计算公式就会变为:
array[n]=array[0]+size*(n-1)
对比很容易发现,从1开始编号比从0开始编号每次获取内存地址都多了一次 减法运算,也就多了一次cpu指令的运行。这也是数组从0下标开始访问一个原因。
其实还有一种可能性,那就是所有现代编程语言的鼻祖:C语言,它是从0开始计数下标的,所以现在所有衍生出来的后代语言也就延续了这个传统。虽然不符合人类的思想,但是符合计算机的原理。当然也有一些语言可以设置为不从下标0开始计算,这里不再展开,有兴趣的可以去搜索一下。
2. 由于数组的连续性,所以在遍历数组的时候非常快,不仅得益于数组的连续性,另外也得益于cpu的缓存,因为cpu读取缓存只能读取连续内存的内容,所以数组的连续性正好符合cpu缓存的指令原理,要知道cpu缓存的速度要比内存的速度快上很多。
劣势
1. 由于数组在内存排列上是连续的,而且要保持这种连续性,所以当增加一个元素或删除一个元素的时候,为了保证连续性,需要做大量元素的移动工作。
举个栗子:要在数组头部插入一个新元素,为了在头部腾出位置,所有的元素都要后移一位,假设元素个数为n,这就导致了时间复杂度为O(n)的一次操作,当然如果是在数组末尾插入新元素,其他所有元素都不必移动,操作的时间复杂度为O(1)。
当然这里有一个技巧:如果你的业务要求并不是数组连续有序的,当在位置k插入元素的时候,只需要把k元素转移到数组末尾,新元素插入到k位置即可。当然仔细沉思一下这种业务场景可能性太小了,数组都可以无序,我直接插入末尾即可,没有必要非得在k位置插入把。~~
当然还有一个特殊场景:如果是多次连续的k位置插入操作,我们完全可以合并为一次“批量插入”操作:把k之后的元素整体移动sum(插入次数)个位置,无需一个个位置移动,把三次操作的时间复杂度合并为一次。
与插入对应的就有删除操作,同理,删除操作数组为了保持连续性,也需要元素的移动。
综上所述,数组在添加和删除元素的场景下劣势比较明显,所以在具体业务场景下应该避免频繁添加和删除的操作。
2. 数组的连续性就要求创建数组的时候,内存必须有相应大小的连续区块,如果不存在,数组就有可能出现创建失败的现象。在某些高级语言中(比如c#,golang,java)就有可能引发一次GC(垃圾回收)操作,GC操作在系统运行中是非常昂贵的,有的语言甚至会挂起所有线程的操作,对外的表现就是“暂停服务”。
3. 数组要求所有元素为同一个类型。在存储数据维度,它可能算是一种劣势,但是为了按照下标快速查找元素,业务中这也是一种优势。仁者见仁智者见智而已。
4. 数组是长度固定的数据结构,所以在原始数组的基础上扩容是不可能的,有的语言可能实现数组的“伪扩容”,为什么说是“伪”呢,因为原理其实是创建了一个容量更大的数组来存放原数组元素,发生了数据复制的过程,只不过对于调用者而已透明而已。
5. 数组有访问越界的可能。我们按照下标访问数组的时候如果下标超出了数组长度,在现代多数高级语言中,直接就会引发异常了,但是一些低级语言比如C 有可能会访问到数组元素以外的数据,因为要访问的内存地址确实存在。
其他
很多编程语言中你会发现“纯数组”并没有提供直接删除元素的方法(例如:c#,golang),而是需要将数组转化为另一种数据结构来实现数组元素的删除。比如在golang种可以转化为slice。这也验证了数组的不变性。
应用场景
我们学习的每个数据结构其实都有对应的适合场景,只不过是场景多少的问题,具体什么时候用,需要我们对该数据结构的特性做深入分析。
关于数组的特性,通过以上介绍可以知道最大的一个亮点就是按照下标访问,那有没有具体业务映射这种特性呢?
1. 相信很多IT人士都遇到过会员机制,每个会员到达一定的经验值就会升级,怎么判断当前的经验是否到达升级条件呢?我们是不是可以这样做:比如当前会员等级为3,判断是否到达等级4的经验值,只需要array[4]的值判断即可,大多数人把配置放到DB,资源耗费太严重。也有的人放到其他容器缓存。但是大部分场景下查询的时间复杂度要比数组大很多。
2. 在分布式底层应用中,我们会有利用一致性哈希方案来解决每个请求交给哪个服务器去处理的场景。有兴趣的同学可以自己去研究一下。其中有一个环节:根据哈希值查找对应的服务器,这是典型的读多写少的应用,而且比较偏底层。如果用其他数据结构来解决大量的查找问题,可能会触碰到性能的瓶颈。而数据按下标访问时间复杂度为O(1)的特性,使得数组在类似这些应用中非常广泛。
c#socket TCP同步网络通信
一、socket简介
socket就是套接字,它是引用网络连接的特殊文件描述符,由三个基本要素组成:
1: AddressFamily(网络类型)
2: SocketType(数据传输类型)
3:ProtocolType(采用的网络协议)
下面是可用于IP通信的套接字组合及其常用的属性和方法
二、socket与网络通信
IP连接领域有两种通信类型:面向连接的和无连接的,这里我要讲的是面向连接的,使用TCP协议来建立两个IP的值端点之间的会话,下面是它的基本步骤。
a:建立一个套接字
b:绑定本机的IP和端口
c:使用listen()方法监听别人发过来的东西
d:如果监听到连接,则可以使用Send/Receive来执行操作
e:完成后使用Close()方法进行关闭
工作原理如图所示:
三、一个简单的同步通信示例
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
43
44
45
46
47
48
49
|
using
System.Text;
using
System.Threading.Tasks;
using
System.Net;
using
System.Net.Sockets;
using
System.Threading;
namespace
socket服务端
{
class
Program
{
static
void
Main(
string
[] args)
{
int
port = 23456;
//端口号
int
recv;
//记录客户端信息的长度
string
address =
"127.0.0.1"
;
//IP地址,指向localhost主机名,常用于程序调试
IPAddress addr = IPAddress.Parse(address);
IPEndPoint ipe =
new
IPEndPoint(addr, port);
Socket socket =
new
Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Bind(ipe);
//绑定端口
socket.Listen(0);
//开始监听,0表示任意数量
Console.WriteLine(
"已启动监听,等待客户端连接。"
);
Socket clientSocket = socket.Accept();
IPEndPoint clientIp = (IPEndPoint)clientSocket.RemoteEndPoint;
//获取远程终结点信息
if
(clientSocket !=
null
)
Console.WriteLine(
"成功与{0}的客户端建立联系"
,clientIp);
while
(
true
)
//用死循环不断执行
{
try
{
byte
[] data =
new
byte
[1024];
recv = clientSocket.Receive(data);
//获取客户端传过来的信息
if
(recv == 0)
break
;
Console.WriteLine(
"客户端发来信息:{0}"
,Encoding.ASCII.GetString(data, 0, recv));
Console.Write(
"输入要发送的信息:"
);
String input = Console.ReadLine();
clientSocket.Send(Encoding.ASCII.GetBytes(input));
}
catch
(Exception ex)
{
Console.WriteLine(
"ERROR:{0}"
, ex.Message);
}
}
Console.WriteLine(
"断开连接"
);
clientSocket.Close();
socket.Close();
}
}
}
|
2.客户端
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
|
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Text;
using
System.Threading.Tasks;
using
System.Net;
using
System.Net.Sockets;
namespace
socket客户端
{
class
Program
{
static
void
Main(
string
[] args)
{
int
port = 23456;
string
address =
"127.0.0.1"
;
byte
[] data =
new
byte
[1024];
IPAddress addr = IPAddress.Parse(address);
IPEndPoint ipe =
new
IPEndPoint(addr, port);
Socket socket =
new
Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Connect(ipe);
Console.WriteLine(
"与服务器连接成功"
);
try
{
while
(
true
)
{
Console.Write(
"输入信息内容:"
);
string
input = Console.ReadLine();
if
(input ==
"exit"
)
break
;
socket.Send(Encoding.ASCII.GetBytes(input));
//发送数据
data =
new
byte
[1024];
int
recv;
string
strData;
recv = socket.Receive(data);
strData = Encoding.ASCII.GetString(data, 0, recv);
Console.WriteLine(
"服务器发来内容:{0}"
,strData);
}
}
catch
(SocketException ex)
{
Console.WriteLine(ex.ToString());
}
Console.WriteLine(
"断开连接..."
);
socket.Shutdown(SocketShutdown.Both);
socket.Close();
Console.ReadKey();
}
}
}
|
接下来对上面一些内容进行解释
a.端口号:32位无符号整形,范围是0~65535,0~1023被系统进程和通信协议使用,1024~49251是用户可以使用的端口
b.IPAddress类: 该类有一个Parse()方法,可以把点分的十进制IP转化为IPAdress类, 它还有四个可读字段
Any:用于代表本地系统可用的任何IP地址
Broadcase:用于代表本地网络的IP广播地址
Loopback:用于代表本地系统的回送地址
None:用于代表本地系统上没有网络接口
c.IPEndPoint是一个端口和IP地址的绑定,可以代表一个服务,用来Socket通信,可以通过两种方法构造
IPEndPoint( long address, int pot);
IPEndPoint( IPAddress address, int pot)'
d. Accept 以同步方式从侦听套接字,在连接请求队列中提取第一个挂起的连接请求,然后创建并返回一个新Socket。 不能使用此返回Socket为接受任何其他连接的连接队列。 但是,可以调用RemoteEndPoint方法所返回的Socket来标识远程主机的网络地址和端口号。
e.Receive()从绑定的 Socket套接字接收数据,将数据存入接收缓冲区。
注:同步通信每发完一个数据包后,需要等待接收方响应后再继续发送下一个数据包
运行时先启动服务器端,再启动客户端
用lambda表达式树替代反射
本节重点不讲反射机制,而是讲lambda表达式树来替代反射中常用的获取属性和方法,来达到相同的效果但却比反射高效。
每个人都知道,用反射调用一个方法或者对属性执行SetValue和GetValue操作的时候都会比直接调用慢很多,这其中设计到CLR中内部的处理,不做深究。然而,我们在某些情况下又无法不使用反射,比如:在一个ORM框架中,你要将一个DataRow转化为一个对象,但你又不清楚该对象有什么属性,这时候你就需要写一个通用的泛型方法来处理,以下代码写得有点恶心,但不妨碍理解意思:
//将DataReader转化为一个对象
private static T GetObj<T>(SqliteDataReader reader) where T : class { T obj = new T(); PropertyInfo[] pros = obj.GetType().GetProperties(); foreach (PropertyInfo item in pros) { try { Int32 Index = reader.GetOrdinal(item.Name); String result = reader.GetString(Index); if (typeof(String) == item.PropertyType) { item.SetValue(obj, result); continue; } if (typeof(DateTime) == item.PropertyType) { item.SetValue(obj, Convert.ToDateTime(result)); continue; } if (typeof(Boolean) == item.PropertyType) { item.SetValue(obj, Convert.ToBoolean(result)); continue; } if (typeof(Int32) == item.PropertyType) { item.SetValue(obj, Convert.ToInt32(result)); continue; } if (typeof(Single) == item.PropertyType) { item.SetValue(obj, Convert.ToSingle(result)); continue; } if (typeof(Single) == item.PropertyType) { item.SetValue(obj, Convert.ToSingle(result)); continue; } if (typeof(Double) == item.PropertyType) { item.SetValue(obj, Convert.ToDouble(result)); continue; } if (typeof(Decimal) == item.PropertyType) { item.SetValue(obj, Convert.ToDecimal(result)); continue; } if (typeof(Byte) == item.PropertyType) { item.SetValue(obj, Convert.ToByte(result)); continue; } } catch (ArgumentOutOfRangeException ex) { continue; } } return obj; }
对于这种情况,其执行效率是特别低下的,具体多慢在下面例子会在.Net Core平台上和.Net Framework4.0运行测试案例.对于以上我举例的情况,效率上我们还可以得到提升。但对于想在运行时修改一下属性的名称或其他操作,反射还是一项特别的神器,因此在某些情况下反射还是无法避免的。
但是对于只是简单的SetValue或者GetValue,包括用反射构造函数,我们可以想一个中继的方法,那就是使用表达式树。对于不理解表达式树的,可以到微软文档查看,点击我。表达式树很容易通过对象模型表示表达式,因此强烈建议学习。查看以下代码:
static void Main() { Dog dog = new Dog(); PropertyInfo propertyInfo = dog.GetType().GetProperty(nameof(dog.Name)); //获取对象Dog的属性 MethodInfo SetterMethodInfo = propertyInfo.GetSetMethod(); //获取属性Name的set方法 ParameterExpression param = Expression.Parameter(typeof(Dog), "param"); Expression GetPropertyValueExp = Expression.Lambda(Expression.Property(param, nameof(dog.Name)), param); Expression<Func<Dog, String>> GetPropertyValueLambda = (Expression<Func<Dog, String>>)GetPropertyValueExp; ParameterExpression paramo = Expression.Parameter(typeof(Dog), "param"); ParameterExpression parami = Expression.Parameter(typeof(String), "newvalue"); MethodCallExpression MethodCallSetterOfProperty = Expression.Call(paramo, SetterMethodInfo, parami); Expression SetPropertyValueExp = Expression.Lambda(MethodCallSetterOfProperty, paramo, parami); Expression<Action<Dog, String>> SetPropertyValueLambda = (Expression<Action<Dog, String>>)SetPropertyValueExp; //创建了属性Name的Get方法表达式和Set方法表达式,当然只是最简单的 Func<Dog, String> Getter = GetPropertyValueLambda.Compile(); Action<Dog, String> Setter = SetPropertyValueLambda.Compile(); Setter?.Invoke(dog, "WLJ"); //我们现在对dog这个对象的Name属性赋值 String dogName = Getter?.Invoke(dog); //获取属性Name的值 Console.WriteLine(dogName); Console.ReadKey(); } public class Dog { public String Name { get; set; } }
以上代码可能很难看得懂,但只要知道我们创建了属性的Get、Set这两个方法就行,其结果最后也能输出狗的名字 WLJ,拥有ExpressionTree的好处是他有一个名为Compile()的方法,它创建一个代表表达式的代码块。现在是最有趣的部分,假设你在编译时不知道类型(在这篇文章中包含的代码我在不同的程序集上创建了一个类型)你仍然可以应用这种技术,我将对于常用的属性的set,get操作进行分装。
/// <summary> /// 属性类,仿造反射中的PropertyInfo /// </summary> public class Property { private readonly PropertyGetter getter; private readonly PropertySetter setter; public String Name { get; private set; } public PropertyInfo Info { get; private set; } public Property(PropertyInfo propertyInfo) { if (propertyInfo == null) throw new NullReferenceException("属性不能为空"); this.Name = propertyInfo.Name; this.Info = propertyInfo; if (this.Info.CanRead) { this.getter = new PropertyGetter(propertyInfo); } if (this.Info.CanWrite) { this.setter = new PropertySetter(propertyInfo); } } /// <summary> /// 获取对象的值 /// </summary> /// <param name="instance"></param> /// <returns></returns> public Object GetValue(Object instance) { return getter?.Invoke(instance); } /// <summary> /// 赋值操作 /// </summary> /// <param name="instance"></param> /// <param name="value"></param> public void SetValue(Object instance, Object value) { this.setter?.Invoke(instance, value); } private static readonly ConcurrentDictionary<Type, Core.Reflection.Property[]> securityCache = new ConcurrentDictionary<Type, Property[]>(); public static Core.Reflection.Property[] GetProperties(Type type) { return securityCache.GetOrAdd(type, t => t.GetProperties().Select(p => new Property(p)).ToArray()); } } /// <summary> /// 属性Get操作类 /// </summary> public class PropertyGetter { private readonly Func<Object, Object> funcGet; public PropertyGetter(PropertyInfo propertyInfo) : this(propertyInfo?.DeclaringType, propertyInfo.Name) { } public PropertyGetter(Type declareType, String propertyName) { if (declareType == null) { throw new ArgumentNullException(nameof(declareType)); } if (propertyName == null) { throw new ArgumentNullException(nameof(propertyName)); } this.funcGet = CreateGetValueDeleagte(declareType, propertyName); } //代码核心部分 private static Func<Object, Object> CreateGetValueDeleagte(Type declareType, String propertyName) { // (object instance) => (object)((declaringType)instance).propertyName var param_instance = Expression.Parameter(typeof(Object)); var body_objToType = Expression.Convert(param_instance, declareType); var body_getTypeProperty = Expression.Property(body_objToType, propertyName); var body_return = Expression.Convert(body_getTypeProperty, typeof(Object)); return Expression.Lambda<Func<Object, Object>>(body_return, param_instance).Compile(); } public Object Invoke(Object instance) { return this.funcGet?.Invoke(instance); } }
public class PropertySetter { private readonly Action<Object, Object> setFunc; public PropertySetter(PropertyInfo property) { if (property == null) { throw new ArgumentNullException(nameof(property)); } this.setFunc = CreateSetValueDelagate(property); } private static Action<Object, Object> CreateSetValueDelagate(PropertyInfo property) { // (object instance, object value) => // ((instanceType)instance).Set_XXX((propertyType)value) //声明方法需要的参数 var param_instance = Expression.Parameter(typeof(Object)); var param_value = Expression.Parameter(typeof(Object)); var body_instance = Expression.Convert(param_instance, property.DeclaringType); var body_value = Expression.Convert(param_value, property.PropertyType); var body_call = Expression.Call(body_instance, property.GetSetMethod(), body_value); return Expression.Lambda<Action<Object, Object>>(body_call, param_instance, param_value).Compile(); } public void Invoke(Object instance, Object value) { this.setFunc?.Invoke(instance, value); } }
在将代码应用到实例:
Dog dog = new Dog(); PropertyInfo propertyInfo = dog.GetType().GetProperty(nameof(dog.Name)); //反射操作 propertyInfo.SetValue(dog, "WLJ"); String result = propertyInfo.GetValue(dog) as String; Console.WriteLine(result); //表达式树的操作 Property property = new Property(propertyInfo); property.SetValue(dog, "WLJ2"); String result2 = property.GetValue(dog) as String; Console.WriteLine(result2);
发现其实现的目的与反射一致,但效率却有明显的提高。
以下测试以下他们两之间的效率。测试代码如下:
Student student = new Student(); PropertyInfo propertyInfo = student.GetType().GetProperty(nameof(student.Name)); Property ExpProperty = new Property(propertyInfo); Int32 loopCount = 1000000; CodeTimer.Initialize(); //测试环境初始化 //下面该方法个执行1000000次 CodeTimer.Time("基础反射", loopCount, () => { propertyInfo.SetValue(student, "Fode",null); }); CodeTimer.Time("lambda表达式树", loopCount, () => { ExpProperty.SetValue(student, "Fode"); }); CodeTimer.Time("直接赋值", loopCount, () => { student.Name = "Fode"; }); Console.ReadKey();
其.Net4.0环境下运行结果如下:
.Net Core环境下运行结果:
从以上结果可以知道,迭代同样的次数反射需要183ms,而用表达式只要34ms,直接赋值需要7ms,在效率上,使用表达式这种方法有显著的提高,您可以看到使用此技术可以完全避免使用反射时的性能损失。反射之所以效率有点低主要取决于其加载的时候时在运行期下,而表达式则在编译期,下篇有空将会介绍用Emit技术优化反射,会比表达式略快一点。
注:对于常用对象的属性,最好将其缓存起来,这样效率会更高。。
ASP.NET MVC如何做一个简单的非法登录拦截
摘要:做网站的时候,经常碰到这种问题,一个没登录的用户,却可以通过localhost:23244/Main/Index的方式进入到网站的内部,查看网站的信息。我们知道,这是极不安全的,那么如何对这样的操作进行拦截呢,这里记录我学到的一个小小方法。
以下是我要记录的正文部分:
开始讲之前声明一点,我目前的能力着实很有限,有些东西并不很懂,也可能讲不清楚,有些知识表述可能是错误的(尽量避免),主要是把我对这部分做法的理解记载下来,以后自己独立开发的时候确保不会忘记。
非法登录拦截,主要用到的是.net mvc里的过滤器。我们每次在执行一个方法时候,实际上程序会预先对我们设置的一些过滤条件进行验证和判断,而不同的过滤器作用的优先级是不同的,在实现这个拦截功能的时候,用到的主要是全局过滤器(关于过滤器的知识,了解并不深入,不详述)。
具体的处理思路是这样的:我们现在App_Start文件夹下的FilterConfig.cs文件中注册一个全局过滤器,这个全局过滤器的作用是——进行登录授权,也就是检查你这个用户是不是已经登录的合法用户,如果不是,那么你做的任何其他操作,系统都不会响应,而是一直把你堵在登录界面。接下来看一段代码:
代码:
using Console.App_Start; using System.Web; using System.Web.Mvc; namespace Console { public class FilterConfig { /// <summary> /// 注册全局过滤器 /// </summary> /// <param name="filters"></param> public static void RegisterGlobalFilters(GlobalFilterCollection filters) { //filters.Add(new HandleErrorAttribute()); //登录授权 filters.Add(new AuthFilter()); } } }
上面的代码,主要看这一句
filters.Add(new AuthFilter());
这句的意思是,我在这里注册了一个名为 AuthFilter的过滤器,每次后台执行某个动作之前,都必须先要通过这个过滤器的审核,审核通过执行某操作,审核不通过有执行某操作。
下面,在App_Start下新建一个名为AuthFilter.cs的类,然后在这里些授权条件,下面看一段代码:
代码如下:
using Console.Util; using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace Console.App_Start { public class AuthFilter:ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext filterContext) { //如果用户未登录,且action未明确标识可跳过登录授权,则跳转到登录页面 if (!CacheUtil.IsLogin&&!filterContext.ActionDescriptor.IsDefined(typeof(AuthEscape),false)) { const string loginUrl = "~/Main/Login"; filterContext.Result = new RedirectResult(loginUrl); } base.OnActionExecuting(filterContext); } } }
以上代码只说明核心的拦截功能的实现,至于余下的一些关于过滤器的使用语法之类的知识点,不会讲述,因为我也不知道呀,只知道是这么写的。
重点看下面这一句:
//如果用户未登录,且action未明确标识可跳过登录授权,则跳转到登录页面 if (!CacheUtil.IsLogin&&!filterContext.ActionDescriptor.IsDefined(typeof(AuthEscape),false))
这是一个条件表达式,前一句 CacheUtil.IsLogin 是一个bool类型的值,为true则表示已经登录,为false则表示未登录,!CacheUtil.IsLogin表示未登录的意思,后一句
filterContext.ActionDescriptor.IsDefined(typeof(AuthEscape),false))
这里重要的其实是这个 AuthEscape,这是一个定义过滤器特性的类,在这个我们只把它作为一个标志,作为一个可以免除登录授权的标志,具体使用是这样的,比如,看下图:
我们在执行任何一个方法之前都会经过全局过滤的过滤,只有已经登录的用户才能执行action方法。但是,因为我们的登录信息是在登录之后才被记录的,那我们的登录操作,登录校验的操作不就也被挡在外面了吗,这样一来,岂不是永远无法登录了吗。所以呀,为了解决这个问题,我们就需要给这两个方法每人发一块免检通行证,也就是在他们头上写一个[AuthEscape],只要有了这个标志,那么上面的那句代码就会返回一个true,如果没有,那么就会返回false。假如既没有登录,又没有免检通行证,那么就会被拦在登录界面上。
AuthEscape.cs的代码如下:
代码:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace Console.App_Start { /// <summary> /// 用于标记无需登录授权验证的Action,无任何实现,在那个action上面标注这个,就可以逃过全局过滤器的过滤 /// </summary> public class AuthEscape:ActionFilterAttribute { } }
是的,这个类里面是空的,因为我们并不需要这里有任何内容,我们只需要在免检的方法上挂上这个类的名字,仅此而已。这一整个流程,可以用如下的示意图来简要表示:
关于这部分呢内容就记录到这里了,希望能帮到你哦。
有需要代码的同学,可以在下面留言邮箱,工作日的时候会尽快发给你。