程序猿修仙之路--數據結構之你是否真的懂數組? c#socket TCP同步網絡通訊 用lambda表達式樹替代反射 ASP.NET MVC如何作一個簡單的非法登陸攔截

程序猿修仙之路--數據結構之你是否真的懂數組?

 

 

數據結構html

但凡IT江湖俠士,算法與數據結構爲必修之課。早有前輩已經明確指出:程序=算法+數據結構  。要想在以後的江湖歷練中通關,數據結構必不可少。數據結構與算法相輔相成,亦是陰陽互補之法。java

開篇golang

    

    說道數組,幾乎每一個IT江湖人士都不陌生,甚至過半人還會很自信覺的它很簡單。 的確,在菜菜所知道的編程語言中幾乎都會有數組的影子。不過它不只僅是一種基礎的數據類型,更是一種基礎的數據結構。若是你覺的對數組足夠了解,那能不能回答一下:算法

數組的本質定義?express

數組的內存結構?編程

數組有什麼優點?c#

數組有什麼劣勢?數組

數組的應用場景?緩存

數組爲何大部分都從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同步網絡通訊

 

1、socket簡介

  socket就是套接字,它是引用網絡鏈接的特殊文件描述符,由三個基本要素組成:

    1: AddressFamily(網絡類型)

    2: SocketType(數據傳輸類型)

    3:ProtocolType(採用的網絡協議)

  下面是可用於IP通訊的套接字組合及其經常使用的屬性和方法

 

2、socket與網絡通訊 

  IP鏈接領域有兩種通訊類型:面向鏈接的和無鏈接的,這裏我要講的是面向鏈接的,使用TCP協議來創建兩個IP的值端點之間的會話,下面是它的基本步驟。

    a:創建一個套接字

    b:綁定本機的IP和端口

    c:使用listen()方法監聽別人發過來的東西

    d:若是監聽到鏈接,則可使用Send/Receive來執行操做

    e:完成後使用Close()方法進行關閉

  工做原理如圖所示:

  

 

3、一個簡單的同步通訊示例

  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
    {

    }
}
複製代碼

 

 是的,這個類裏面是空的,由於咱們並不須要這裏有任何內容,咱們只須要在免檢的方法上掛上這個類的名字,僅此而已。這一整個流程,能夠用以下的示意圖來簡要表示:

 

關於這部分呢內容就記錄到這裏了,但願能幫到你哦。

有須要代碼的同窗,能夠在下面留言郵箱,工做日的時候會盡快發給你。

相關文章
相關標籤/搜索