C#代码审计实战+前置知识

C#了解

菜鸟教程:https://www.runoob.com/csharp/csharp-intro.html

C# 基于 C 和 C++ 编程语言,是一个简单的、现代的、通用的、面向对象的编程语言,它是由微软(Microsoft)开发的,由 Ecma 和 ISO 核准认可的。

C# 是由 Anders Hejlsberg 和他的团队在 .Net 框架开发期间开发的。

C# 是专为公共语言基础结构(CLI)设计的。CLI 由可执行代码和运行时环境组成,允许在不同的计算机平台和体系结构上使用各种高级语言。

C# 成为一种广泛应用的专业语言的原因:

  • 现代的、通用的编程语言。
  • 面向对象。
  • 面向组件。
  • 容易学习。
  • 结构化语言。
  • 它产生高效率的程序。
  • 它可以在多种计算机平台上编译。
  • .Net 框架的一部分。

C# 一些重要的功能:

  • 布尔条件(Boolean Conditions)
  • 自动垃圾回收(Automatic Garbage Collection)
  • 标准库(Standard Library)
  • 组件版本(Assembly Versioning)
  • 属性(Properties)和事件(Events)
  • 委托(Delegates)和事件管理(Events Management)
  • 易于使用的泛型(Generics)
  • 索引器(Indexers)
  • 条件编译(Conditional Compilation)
  • 简单的多线程(Multithreading)
  • LINQ 和 Lambda 表达式
  • 集成 Windows

环境

创建 C# 编程所需的工具。

C# 是 .Net 框架的一部分,且用于编写 .Net 应用程序。因此,先了解一下 C# 与 .Net 框架之间的关系。

.Net 框架(.Net Framework)

.Net 框架是一个创新的平台,能帮您编写出下面类型的应用程序:

  • Windows 应用程序
  • Web 应用程序
  • Web 服务

.Net 框架应用程序是多平台的应用程序。

框架的设计方式使它适用于下列各种语言:C#、C++、Visual Basic、Jscript、COBOL 等等。

所有这些语言可以访问框架,彼此之间也可以互相交互。

.Net 框架由一个巨大的代码库组成,用于 C# 等客户端语言。下面列出一些 .Net 框架的组件:

  • 公共语言运行库(Common Language Runtime - CLR)
  • .Net 框架类库(.Net Framework Class Library)
  • 公共语言规范(Common Language Specification)
  • 通用类型系统(Common Type System)
  • 元数据(Metadata)和组件(Assemblies)
  • Windows 窗体(Windows Forms)
  • ASP.Net 和 ASP.Net AJAX
  • ADO.Net
  • Windows 工作流基础(Windows Workflow Foundation - WF)
  • Windows 显示基础(Windows Presentation Foundation)
  • Windows 通信基础(Windows Communication Foundation - WCF)
  • LINQ

程序结构

几点值得注意:

  • C# 是大小写敏感的。
  • 所有的语句和表达式必须以分号(;)结尾。
  • 程序的执行从 Main 方法开始。
  • 与 Java 不同的是,文件名可以不同于类的名称。

示例:C# 的最小的程序结构。C# 文件的后缀为 .cs

using System;
namespace HelloWorldApplication
{
   class HelloWorld
   {
      static void Main(string[] args)
      {
         /* 我的第一个 C# 程序*/
         Console.WriteLine("Hello World");
         Console.ReadKey();
        //单行注释
      }
   }
}
  • 程序的第一行 using System; - using 关键字用于在程序中包含 System 命名空间。 一个程序一般有多个 using 语句。

  • 下一行是 namespace 声明。一个 namespace 里包含了一系列的类。HelloWorldApplication 命名空间包含了类 HelloWorld

  • 下一行是 class 声明(声明一个类)。类 HelloWorld 包含了程序使用的数据和方法声明。类一般包含多个方法。方法定义了类的行为。在这里,HelloWorld 类只有一个 Main 方法。

  • 下一行定义了 Main 方法,是所有 C# 程序的 入口点Main 方法说明当执行时 类将做什么动作。

  • 下一行 // 将会被编译器忽略,且它会在程序中添加额外的 注释

  • Main 方法通过语句 Console.WriteLine(“Hello World”); 指定了它的行为。

    WriteLine 是一个定义在 System 命名空间中的 Console 类的一个方法。该语句会在屏幕上显示消息 “Hello World”。

  • 最后一行 Console.ReadKey(); 是针对 VS.NET 用户的。这使得程序会等待一个按键的动作,防止程序从 Visual Studio .NET 启动时屏幕会快速运行并关闭。

基本语法

C# 是一种面向对象的编程语言。在面向对象的程序设计方法中,程序由各种相互交互的对象组成。相同种类的对象通常具有相同的类型,或者说,是在相同的 class 中。

标识符

标识符是用来识别类、变量、函数或任何其它用户定义的项目。

在 C# 中,类的命名必须遵循如下基本规则:

  • 标识符必须以字母、下划线或 @ 开头,后面可以跟一系列的字母、数字( 0 - 9 )、下划线( _ )、@。
  • 标识符不能是 C# 关键字。除非它们有一个 @ 前缀。 例如,@if 是有效的标识符,但 if 不是,因为 if 是关键字。
  • 标识符必须区分大小写。大写字母和小写字母被认为是不同的字母。
  • 不能与 C# 的类库名称相同。

示例:Rectangle(矩形),具有 length 和 width 属性,可能需要接受这些属性值、计算面积和显示细节。

using System;
namespace RectangleApplication
{
    class Rectangle
    {
        // 成员变量
        double length;
        double width;
      //成员函数
        public void Acceptdetails()
        {
            length = 4.5;    
            width = 3.5;
        }
        public double GetArea()
        {
            return length * width;
        }
        public void Display()
        {
            Console.WriteLine("Length: {0}", length);
            Console.WriteLine("Width: {0}", width);
            Console.WriteLine("Area: {0}", GetArea());
        }
    }
    
    class ExecuteRectangle
    {
        static void Main(string[] args)
        {//实例化 Rectangle 类
            Rectangle r = new Rectangle();
            r.Acceptdetails();
            r.Display();
            Console.ReadLine();
        }
    }
}

数据类型

变量分为以下几种类型:

  • 值类型(Value types)
  • 引用类型(Reference types)
  • 指针类型(Pointer types)

1、值类型(Value types)

值类型变量可以直接分配给一个值。它们是从类 System.ValueType 中派生的。

值类型直接包含数据。比如 int、char、float,它们分别存储数字、字符、浮点数。当您声明一个 int 类型时,系统分配内存来存储值。

下表列出了 C# 2010 中可用的值类型:

表达式 sizeof(type) 产生以字节为单位存储对象或类型的存储尺寸

using System;

namespace DataTypeApplication
{
   class Program
   {
      static void Main(string[] args)
      {
         Console.WriteLine("Size of int: {0}", sizeof(int));
         Console.ReadLine();
      }
   }
}
// 运行结果
// Size of int: 4

2、引用类型(Reference types)

指的是一个内存位置。内置的 引用类型有:objectdynamicstring

(1)对象(Object)类型

是 C# 通用类型系统(Common Type System - CTS)中所有数据类型的终极基类,Object 是 System.Object 类的别名。

所以对象(Object)类型可以被分配任何其他类型(值类型、引用类型、预定义类型或用户自定义类型)的值。

但是,在分配值之前,需要先进行类型转换。

当一个值类型转换为对象类型时,则被称为 装箱;反之,则被称为 拆箱

object obj;
obj = 100; // 这是装箱

(2)动态(Dynamic)类型

您可以存储任何类型的值在动态数据类型变量中。这些变量的类型检查是在运行时发生的。

声明动态类型的语法:

dynamic <variable_name> = value;

//例如
dynamic d = 20;

动态类型与对象类型相似,但是对象类型变量的类型检查是在编译时发生的,而动态类型变量的类型检查是在运行时发生的。

(3)字符串(String)类型

可以给变量分配任何字符串值。

字符串(String)类型是 System.String 类的别名。它是从对象(Object)类型派生的。

字符串(String)类型的值可以通过两种形式进行分配:引号和 @引号。

String str = "runoob.com";

//一个 @引号字符串
@"runoob.com";

//C# string 字符串的前面可以加 @(称作"逐字字符串")将转义字符(\)当作普通字符对待,比如
string str = @"C:\Windows";//等价于 string str = "C:\\Windows";

//@ 字符串中可以任意换行,换行符及缩进空格都计算在字符串长度之内。
string str = @"<script type=""text/javascript"">
    <!--
    -->
</script>";

用户自定义引用类型有:class、interface 或 delegate。

3、指针类型(Pointer types)

指针类型变量存储另一种类型的内存地址。与 C 或 C++ 中的指针有相同的功能。

声明指针类型的语法:

type* identifier;

//举例
char* cptr;
int* iptr;
类型转换
  • 隐式类型转换 - 这些转换是 C# 默认的以安全方式进行的转换, 不会导致数据丢失。例如,从小的整数类型转换为大的整数类型,从派生类转换为基类。
  • 显式类型转换 - 显式类型转换,即强制类型转换。显式转换需要强制转换运算符,而且强制转换会造成数据丢失。

下面的实例显示了一个显式的类型转换:

namespace TypeConversionApplication
{
  class ExplicitConversion
  {
    static void Main(string[] args)
    {
      double d = 5673.74;
      int i;

      // 强制转换 double 为 int
      i = (int)d;
      Console.WriteLine(i);
      Console.ReadKey();
      
   }
  }
}

//结果:
5673

内置类型转换方法:
在这里插入图片描述
在这里插入图片描述

变量 常量

一个变量只不过是一个供程序操作的存储区的名字。

在 C# 中,每个变量都有一个特定的类型,类型决定了变量的内存大小和布局。范围内的值可以存储在内存中,可以对变量进行一系列操作。

C# 中提供的基本的值类型大致可以分为以下几类:

1、变量定义

2、变量初始化

3、接受来自用户的值

System 命名空间中的 Console 类提供了一个函数 ReadLine(),用于接收来自用户的输入,并把它存储到一个变量中。

int num;
num = Convert.ToInt32(Console.ReadLine());

函数 Convert.ToInt32() 把用户输入的数据转换为 int 数据类型,因为 Console.ReadLine() 只接受字符串格式的数据。

常量:

常量可以被当作常规的变量,只是它们的值在定义后不能被修改。

常量是使用 const 关键字来定义的 。定义一个常量的语法如下:

const <data_type> <constant_name> = value;

常量可以是任何基本数据类型,比如整数常量、浮点常量、字符常量或者字符串常量,还有枚举常量。

可空类型(Nullable)

一个特殊的数据类型,nullable 类型(可空类型),可空类型可以表示其基础值类型正常范围内的值,再加上一个 null 值。

例如,Nullable< Int32 >,读作"可空的 Int32",可以被赋值为 -2,147,483,648 到 2,147,483,647 之间的任意值,也可以被赋值为 null 值。类似的,Nullable< bool > 变量可以被赋值为 true 或 false 或 null。

? 单问号用于对 int、double、bool 等无法直接赋值为 null 的数据类型进行 null 的赋值,意思是这个数据类型是 Nullable 类型的。

int? i = 3;

//等同于:
Nullable<int> i = new Nullable<int>(3);
int i; //默认值0
int? ii; //默认值null

null合并运算符**??** 用于定义可空类型和引用类型的默认值;双问号用于判断一个变量在为 null 的时候返回一个指定的值。

 		 double? num1 = null;
         double? num2 = 3.14157;
         double num3;
         num3 = num1 ?? 5.34;      // num1 如果为空值则返回 5.34
结构体 (Struct)

结构体是值类型数据结构。

它使得一个单一变量可以存储各种数据类型的相关数据。struct 关键字用于创建结构体。

结构体是用来代表一个记录。假设您想跟踪图书馆中书的动态。您可能想跟踪每本书的以下属性:

  • Title
  • Author
  • Subject
  • Book ID

struct 语句为程序定义了一个带有多个成员的新的数据类型。

using System;
using System.Text;
     
struct Books
{
   private string title;
   private string author;
   private string subject;
   private int book_id;
   public void setValues(string t, string a, string s, int id)
   {
      title = t;
      author = a;
      subject = s;
      book_id =id; 
   }
   public void display()
   {
      Console.WriteLine("Title : {0}", title);
      Console.WriteLine("Author : {0}", author);
      Console.WriteLine("Subject : {0}", subject);
      Console.WriteLine("Book_id :{0}", book_id);
   }

};  

public class testStructure
{
   public static void Main(string[] args)
   {

      Books Book1 = new Books(); /* 声明 Book1,类型为 Books */
      Books Book2 = new Books(); /* 声明 Book2,类型为 Books */

      /* book 1 详述 */
      Book1.setValues("C Programming",
      "Nuha Ali", "C Programming Tutorial",6495407);

      /* book 2 详述 */
      Book2.setValues("Telecom Billing",
      "Zara Ali", "Telecom Billing Tutorial", 6495700);

      /* 打印 Book1 信息 */
      Book1.display();

      /* 打印 Book2 信息 */
      Book2.display(); 

      Console.ReadKey();

   }
}

C# 结构的特点

您已经用了一个简单的名为 Books 的结构。在 C# 中的结构与传统的 C 或 C++ 中的结构不同。C# 中的结构有以下特点:

  • 结构可带有方法、字段、索引、属性、运算符方法和事件。
  • 结构可定义构造函数,但不能定义析构函数。但是,您不能为结构定义无参构造函数。无参构造函数(默认)是自动定义的,且不能被改变。
  • 与类不同,结构不能继承其他的结构或类。
  • 结构不能作为其他结构或类的基础结构。
  • 结构可实现一个或多个接口。
  • 结构成员不能指定为 abstract、virtual 或 protected。
  • 当您使用 New 操作符创建一个结构对象时,会调用适当的构造函数来创建结构。与类不同,结构可以不使用 New 操作符即可被实例化。
  • 如果不使用 New 操作符,只有在所有的字段都被初始化之后,字段才被赋值,对象才被使用。

类 vs 结构

类和结构有以下几个基本的不同点:

  • 类是引用类型,结构是值类型。
  • 结构不支持继承。
  • 结构不能声明默认的构造函数。
枚举

枚举是一组命名整型常量。枚举类型是使用 enum 关键字声明的。

C# 枚举是值类型。换句话说,枚举包含自己的值,且不能继承或传递继承。

枚举列表中的每个符号代表一个整数值,一个比它前面的符号大的整数值。默认情况下,第一个枚举符号的值是 0。

using System;

public class EnumTest
{
    enum Day { Sun, Mon, Tue, Wed, Thu, Fri, Sat };

    static void Main()
    {
        int x = (int)Day.Sun;
        int y = (int)Day.Fri;
        Console.WriteLine("Sun = {0}", x);
        Console.WriteLine("Fri = {0}", y);
    }
}

//结果
Sun = 0
Fri = 5

运算符

运算符是一种告诉编译器执行特定的数学或逻辑操作的符号。C# 有丰富的内置运算符,分类如下:

  • 算术运算符
  • 关系运算符
  • 逻辑运算符
  • 位运算符
  • 赋值运算符

  • 其他运算符

运算符优先级

判断 循环


无限循环:

for 循环在传统意义上可用于实现无限循环。由于构成循环的三个表达式中任何一个都不是必需的,您可以将某些条件表达式留空来构成一个无限循环。

 for (; ; )
            {
                Console.WriteLine("Hey! I am Trapped");
            }

一些

C# 封装

C# 方法

C# 数组

C# 类

C# 继承

C# 多态性

C# 运算符重载

C# 接口

C# 正则表达式: https://www.runoob.com/csharp/csharp-regular-expressions.html

C# 异常处理

命名空间

在一个命名空间中声明的类的名称与另一个命名空间中声明的相同的类的名称不冲突。

using System;
namespace first_space
{
   class namespace_cl
   {
      public void func()
      {
         Console.WriteLine("Inside first_space");
      }
   }
}
namespace second_space
{
   class namespace_cl
   {
      public void func()
      {
         Console.WriteLine("Inside second_space");
      }
   }
}   
class TestClass
{
   static void Main(string[] args)
   {
      first_space.namespace_cl fc = new first_space.namespace_cl();
      second_space.namespace_cl sc = new second_space.namespace_cl();
      fc.func();
      sc.func();
      Console.ReadKey();
   }
}

使用 using 命名空间指令

using System;
using first_space;
using second_space;

namespace first_space
{
   class abc
   {
      public void func()
      {
         Console.WriteLine("Inside first_space");
      }
   }
}
namespace second_space
{
   class efg
   {
      public void func()
      {
         Console.WriteLine("Inside second_space");
      }
   }
}   
class TestClass
{
   static void Main(string[] args)
   {
      abc fc = new abc();
      efg sc = new efg();
      fc.func();
      sc.func();
      Console.ReadKey();
   }
}

命名空间可以被嵌套,可以在一个命名空间内定义另一个命名空间,使用点(.)运算符访问嵌套的命名空间的成员。

预处理器指令

预处理器指令指导编译器在实际编译开始之前对信息进行预处理。

所有的预处理器指令都是以 # 开始。且在一行上,只有空白字符可以出现在预处理器指令之前。预处理器指令不是语句,所以它们不以分号 ; 结束。

C# 编译器没有一个单独的预处理器,但是,指令被处理时就像是有一个单独的预处理器一样。在 C# 中,预处理器指令用于在条件编译中起作用。与 C 和 C++ 不同的是,它们不是用来创建宏。一个预处理器指令必须是该行上的唯一指令。

C# 预处理器指令列表:

#define DEBUG
#define VC_V10
using System;
public class TestClass
{
   public static void Main()
   {

      #if (DEBUG && !VC_V10)
         Console.WriteLine("DEBUG is defined");
      #elif (!DEBUG && VC_V10)
         Console.WriteLine("VC_V10 is defined");
      #elif (DEBUG && VC_V10)
         Console.WriteLine("DEBUG and VC_V10 are defined");
      #else
         Console.WriteLine("DEBUG and VC_V10 are not defined");
      #endif
      Console.ReadKey();
   }
}

文件的输入与输出

一个 文件 是一个存储在磁盘中带有指定名称和目录路径的数据集合。当打开文件进行读写时,它变成一个

从根本上说,流是通过通信路径传递的字节序列。有两个主要的流:输入流输出流输入流用于从文件读取数据(读操作),输出流用于向文件写入数据(写操作)。

https://www.runoob.com/csharp/csharp-file-io.html

特性(Attribute)

特性(Attribute)是用于在运行时传递程序中各种元素(比如类、方法、结构、枚举、组件等)的行为信息的声明性标签。您可以通过使用特性向程序添加声明性信息。一个声明性标签是通过放置在它所应用的元素前面的方括号([ ])来描述的。

特性(Attribute)用于添加元数据,如编译器指令和注释、描述、方法、类等其他信息。

//语法格式
[attribute(positional_parameters, name_parameter = value, ...)]
element

特性(Attribute)的名称和值是在方括号内规定的,放置在它所应用的元素之前。positional_parameters 规定必需的信息,name_parameter 规定可选的信息。

.Net 框架提供了两种类型的特性:预定义特性 和 自定义特性。


1、预定义特性(Attribute)

.Net 框架提供了三种预定义特性:

  • AttributeUsage

描述了如何使用一个自定义特性类。它规定了特性可应用到的项目的类型。

[AttributeUsage(
   validon,
   AllowMultiple=allowmultiple,
   Inherited=inherited
)]

1)参数 validon 规定特性可被放置的语言元素。它是枚举器 AttributeTargets 的值的组合。默认值是 AttributeTargets.All

2)参数 allowmultiple(可选的)为该特性的 AllowMultiple 属性(property)提供一个布尔值。如果为 true,则该特性是多用的。默认值是 false(单用的)。

3)参数 inherited(可选的)为该特性的 Inherited 属性(property)提供一个布尔值。如果为 true,则该特性可被派生类继承。默认值是 false(不被继承)。

  • Conditional

标记了一个条件方法,其执行依赖于指定的预处理标识符。

它会引起方法调用的条件编译,取决于指定的值,比如 Debug 或 Trace 。例如,当调试代码时显示变量的值。

[Conditional(
   conditionalSymbol
)]

//例如
[Conditional("DEBUG")]
  • Obsolete

这个预定义特性标记了不应被使用的程序实体。它可以让您通知编译器丢弃某个特定的目标元素。例如,当一个新方法被用在一个类中,

但是您仍然想要保持类中的旧方法,您可以通过显示一个应该使用新方法,而不是旧方法的消息,来把它标记为 obsolete(过时的)。

1)参数 message,是一个字符串,描述项目为什么过时以及该替代使用什么。

2)参数 iserror,是一个布尔值。如果该值为 true,编译器应把该项目的使用当作一个错误。默认值是 false(编译器生成一个警告)。

using System;
public class MyClass
{
   [Obsolete("Don't use OldMethod, use NewMethod instead", true)]
   static void OldMethod()
   { 
      Console.WriteLine("It is the old method");
   }
   static void NewMethod()
   { 
      Console.WriteLine("It is the new method"); 
   }
   public static void Main()
   {
      OldMethod();
   }
}

//运行结果
Don't use OldMethod, use NewMethod instead

2、创建自定义特性(Attribute)

.Net 框架允许创建自定义特性,用于存储声明性的信息,且可在运行时被检索。

该信息根据设计标准和应用程序需要,可与任何目标元素相关。

创建并使用自定义特性包含四个步骤:

  • 声明自定义特性
  • 构建自定义特性
  • 在目标程序元素上应用自定义特性
  • 通过反射访问特性

最后一个步骤包含编写一个简单的程序来读取元数据以便查找各种符号。元数据是用于描述其他数据的数据和信息。该程序应使用反射来在运行时访问特性。我们将在下一章详细讨论这点。

1)声明自定义特性

一个新的自定义特性应派生自 System.Attribute 类。例如:

// 一个自定义特性 BugFix 被赋给类及其成员
[AttributeUsage(AttributeTargets.Class |
AttributeTargets.Constructor |
AttributeTargets.Field |
AttributeTargets.Method |
AttributeTargets.Property,
AllowMultiple = true)]

public class DeBugInfo : System.Attribute

在上面的代码中,我们已经声明了一个名为 DeBugInfo 的自定义特性。

2)构建自定义特性

让我们构建一个名为 DeBugInfo 的自定义特性,该特性将存储调试程序获得的信息。它存储下面的信息:

  • bug 的代码编号
  • 辨认该 bug 的开发人员名字
  • 最后一次审查该代码的日期
  • 一个存储了开发人员标记的字符串消息

我们的 DeBugInfo 类将带有三个用于存储前三个信息的私有属性(property)和一个用于存储消息的公有属性(property)。所以 bug 编号、开发人员名字和审查日期将是 DeBugInfo 类的必需的定位( positional)参数,消息将是一个可选的命名(named)参数。

每个特性必须至少有一个构造函数。必需的定位( positional)参数应通过构造函数传递。下面的代码演示了 DeBugInfo 类:

// 一个自定义特性 BugFix 被赋给类及其成员
[AttributeUsage(AttributeTargets.Class |
AttributeTargets.Constructor |
AttributeTargets.Field |
AttributeTargets.Method |
AttributeTargets.Property,
AllowMultiple = true)]

public class DeBugInfo : System.Attribute
{
  private int bugNo;
  private string developer;
  private string lastReview;
  public string message;

  public DeBugInfo(int bg, string dev, string d)
  {
      this.bugNo = bg;
      this.developer = dev;
      this.lastReview = d;
  }

  public int BugNo
  {
      get
      {
          return bugNo;
      }
  }
  public string Developer
  {
      get
      {
          return developer;
      }
  }
  public string LastReview
  {
      get
      {
          return lastReview;
      }
  }
  public string Message
  {
      get
      {
          return message;
      }
      set
      {
          message = value;
      }
  }
}

3)应用自定义特性

通过把特性放置在紧接着它的目标之前,来应用该特性:

[DeBugInfo(45, "Zara Ali", "12/8/2012", Message = "Return type mismatch")]
[DeBugInfo(49, "Nuha Ali", "10/10/2012", Message = "Unused variable")]
class Rectangle
{
  // 成员变量
  protected double length;
  protected double width;
  public Rectangle(double l, double w)
  {
      length = l;
      width = w;
  }
  [DeBugInfo(55, "Zara Ali", "19/10/2012",
  Message = "Return type mismatch")]
  public double GetArea()
  {
      return length * width;
  }
  [DeBugInfo(56, "Zara Ali", "19/10/2012")]
  public void Display()
  {
      Console.WriteLine("Length: {0}", length);
      Console.WriteLine("Width: {0}", width);
      Console.WriteLine("Area: {0}", GetArea());
  }
}

反射(Reflection)

反射指程序可以访问、检测和修改它本身状态或行为的一种能力。

程序集包含模块,而模块包含类型,类型又包含成员。反射则提供了封装程序集、模块和类型的对象。

可以使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现有对象中获取类型。然后,可以调用类型的方法或访问其字段和属性。

优点:

  • 1、反射提高了程序的灵活性和扩展性。
  • 2、降低耦合性,提高自适应能力。
  • 3、它允许程序创建和控制任何类的对象,无需提前硬编码目标类。

缺点:

  • 1、性能问题:使用反射基本上是一种解释操作,用于字段和方法接入时要远慢于直接代码。因此反射机制主要应用在对灵活性和拓展性要求很高的系统框架上,普通程序不建议使用。
  • 2、使用反射会模糊程序内部逻辑;程序员希望在源代码中看到程序的逻辑,反射却绕过了源代码的技术,因而会带来维护的问题,反射代码比相应的直接代码更复杂。

反射(Reflection)有下列用途:

  • 它允许在运行时查看特性(attribute)信息。
  • 它允许审查集合中的各种类型,以及实例化这些类型。
  • 它允许延迟绑定的方法和属性(property)。
  • 它允许在运行时创建新类型,然后使用这些类型执行一些任务。

1、查看元数据

使用反射(Reflection)可以查看特性(attribute)信息。

using System;

[AttributeUsage(AttributeTargets.All)]
public class HelpAttribute : System.Attribute
{
   public readonly string Url;

   public string Topic  // Topic 是一个命名(named)参数
   {
      get
      {
         return topic;
      }
      set
      {

         topic = value;
      }
   }

   public HelpAttribute(string url)  // url 是一个定位(positional)参数
   {
      this.Url = url;
   }

   private string topic;
}
[HelpAttribute("Information on the class MyClass")]
class MyClass
{
}

namespace AttributeAppl
{
   class Program
   {
      static void Main(string[] args)
      {
         System.Reflection.MemberInfo info = typeof(MyClass);// System.Reflection  类的  MemberInfo  对象需要被初始化,用于发现与类相关的特性(attribute)。
         object[] attributes = info.GetCustomAttributes(true);
         for (int i = 0; i < attributes.Length; i++)
         {
            System.Console.WriteLine(attributes[i]);
         }
         Console.ReadKey();

      }
   }
}
//当上面的代码被编译和执行时,它会显示附加到类 MyClass 上的自定义特性:
HelpAttribute

实例:

using System;
using System.Reflection;
namespace BugFixApplication
{
   // 一个自定义特性 BugFix 被赋给类及其成员
   [AttributeUsage(AttributeTargets.Class |
   AttributeTargets.Constructor |
   AttributeTargets.Field |
   AttributeTargets.Method |
   AttributeTargets.Property,
   AllowMultiple = true)]

   public class DeBugInfo : System.Attribute
   {
      private int bugNo;
      private string developer;
      private string lastReview;
      public string message;

      public DeBugInfo(int bg, string dev, string d)
      {
         this.bugNo = bg;
         this.developer = dev;
         this.lastReview = d;
      }

      public int BugNo
      {
         get
         {
            return bugNo;
         }
      }
      public string Developer
      {
         get
         {
            return developer;
         }
      }
      public string LastReview
      {
         get
         {
            return lastReview;
         }
      }
      public string Message
      {
         get
         {
            return message;
         }
         set
         {
            message = value;
         }
      }
   }
   [DeBugInfo(45, "Zara Ali", "12/8/2012",
        Message = "Return type mismatch")]
   [DeBugInfo(49, "Nuha Ali", "10/10/2012",
        Message = "Unused variable")]
   class Rectangle
   {
      // 成员变量
      protected double length;
      protected double width;
      public Rectangle(double l, double w)
      {
         length = l;
         width = w;
      }
      [DeBugInfo(55, "Zara Ali", "19/10/2012",
           Message = "Return type mismatch")]
      public double GetArea()
      {
         return length * width;
      }
      [DeBugInfo(56, "Zara Ali", "19/10/2012")]
      public void Display()
      {
         Console.WriteLine("Length: {0}", length);
         Console.WriteLine("Width: {0}", width);
         Console.WriteLine("Area: {0}", GetArea());
      }
   }//end class Rectangle  
   
   class ExecuteRectangle
   {
      static void Main(string[] args)
      {
         Rectangle r = new Rectangle(4.5, 7.5);
         r.Display();
         Type type = typeof(Rectangle);
         // 遍历 Rectangle 类的特性
         foreach (Object attributes in type.GetCustomAttributes(false))
         {
            DeBugInfo dbi = (DeBugInfo)attributes;
            if (null != dbi)
            {
               Console.WriteLine("Bug no: {0}", dbi.BugNo);
               Console.WriteLine("Developer: {0}", dbi.Developer);
               Console.WriteLine("Last Reviewed: {0}",
                                        dbi.LastReview);
               Console.WriteLine("Remarks: {0}", dbi.Message);
            }
         }
         
         // 遍历方法特性
         foreach (MethodInfo m in type.GetMethods())
         {
            foreach (Attribute a in m.GetCustomAttributes(true))
            {
               DeBugInfo dbi = (DeBugInfo)a;
               if (null != dbi)
               {
                  Console.WriteLine("Bug no: {0}, for Method: {1}",
                                                dbi.BugNo, m.Name);
                  Console.WriteLine("Developer: {0}", dbi.Developer);
                  Console.WriteLine("Last Reviewed: {0}",
                                                dbi.LastReview);
                  Console.WriteLine("Remarks: {0}", dbi.Message);
               }
            }
         }
         Console.ReadLine();
      }
   }
}

当上面的代码被编译和执行时,它会产生下列结果:

Length: 4.5
Width: 7.5
Area: 33.75
Bug No: 49
Developer: Nuha Ali
Last Reviewed: 10/10/2012
Remarks: Unused variable
Bug No: 45
Developer: Zara Ali
Last Reviewed: 12/8/2012
Remarks: Return type mismatch
Bug No: 55, for Method: GetArea
Developer: Zara Ali
Last Reviewed: 19/10/2012
Remarks: Return type mismatch
Bug No: 56, for Method: Display
Developer: Zara Ali
Last Reviewed: 19/10/2012
Remarks: 

属性

属性(Property) 是类(class)、结构(structure)和接口(interface)的命名(named)成员。类或结构中的成员变量或方法称为 域(Field)。属性(Property)是域(Field)的扩展,且可使用相同的语法来访问。它们使用 访问器(accessors) 让私有域的值可被读写或操作。

属性(Property)不会确定存储位置。相反,它们具有可读写或计算它们值的 访问器(accessors)

例如,有一个名为 Student 的类,带有 age、name 和 code 的私有域。我们不能在类的范围以外直接访问这些域,但是我们可以拥有访问这些私有域的属性。

属性(Property)的**访问器(accessor)**包含有助于获取(读取或计算)或设置(写入)属性的可执行语句。访问器(accessor)声明可包含一个 get 访问器、一个 set 访问器,或者同时包含二者。

using System;
namespace runoob
{
   class Student
   {

      private string code = "N.A";
      private string name = "not known";
      private int age = 0;

      // 声明类型为 string 的 Code 属性
      public string Code
      {
         get
         {
            return code;
         }
         set
         {
            code = value;
         }
      }
   
      // 声明类型为 string 的 Name 属性
      public string Name
      {
         get
         {
            return name;
         }
         set
         {
            name = value;
         }
      }

      // 声明类型为 int 的 Age 属性
      public int Age
      {
         get
         {
            return age;
         }
         set
         {
            age = value;
         }
      }
      public override string ToString()
      {
         return "Code = " + Code +", Name = " + Name + ", Age = " + Age;
      }
    }
    class ExampleDemo
    {
      public static void Main()
      {
         // 创建一个新的 Student 对象
         Student s = new Student();
            
         // 设置 student 的 code、name 和 age
         s.Code = "001";
         s.Name = "Zara";
         s.Age = 9;
         Console.WriteLine("Student Info: {0}", s);
         // 增加年龄
         s.Age += 1;
         Console.WriteLine("Student Info: {0}", s);
         Console.ReadKey();
       }
   }
}
//运行结果:
Student Info: Code = 001, Name = Zara, Age = 9
Student Info: Code = 001, Name = Zara, Age = 10

抽象属性(Abstract Properties):

抽象类可拥有抽象属性,这些属性应在派生类中被实现。

using System;
namespace runoob
{
   public abstract class Person
   {
      public abstract string Name
      {
         get;
         set;
      }
      public abstract int Age
      {
         get;
         set;
      }
   }
   class Student : Person
   {

      private string code = "N.A";
      private string name = "N.A";
      private int age = 0;

      // 声明类型为 string 的 Code 属性
      public string Code
      {
         get
         {
            return code;
         }
         set
         {
            code = value;
         }
      }
   
      // 声明类型为 string 的 Name 属性
      public override string Name
      {
         get
         {
            return name;
         }
         set
         {
            name = value;
         }
      }

      // 声明类型为 int 的 Age 属性
      public override int Age
      {
         get
         {
            return age;
         }
         set
         {
            age = value;
         }
      }
      public override string ToString()
      {
         return "Code = " + Code +", Name = " + Name + ", Age = " + Age;
      }
   }
   class ExampleDemo
   {
      public static void Main()
      {
         // 创建一个新的 Student 对象
         Student s = new Student();
            
         // 设置 student 的 code、name 和 age
         s.Code = "001";
         s.Name = "Zara";
         s.Age = 9;
         Console.WriteLine("Student Info:- {0}", s);
         // 增加年龄
         s.Age += 1;
         Console.WriteLine("Student Info:- {0}", s);
         Console.ReadKey();
       }
   }
}

//运行结果:
Student Info: Code = 001, Name = Zara, Age = 9
Student Info: Code = 001, Name = Zara, Age = 10

索引器(Indexer)

索引器(Indexer) 允许一个对象可以像数组一样使用下标的方式来访问。

为类定义一个索引器时,该类的行为就会像一个 虚拟数组(virtual array) 一样。可以使用数组访问运算符 [ ] 来访问该类的的成员。

一维索引器的语法如下:

element-type this[int index] 
{
   // get 访问器
   get 
   {
      // 返回 index 指定的值
   }

   // set 访问器
   set 
   {
      // 设置 index 指定的值 
   }
}

索引器(Indexer)的用途:

索引器的行为的声明在某种程度上类似于属性(property)。

就像属性(property),您可使用 getset 访问器来定义索引器。但是,属性返回或设置一个特定的数据成员,而索引器返回或设置对象实例的一个特定值。换句话说,它把实例数据分为更小的部分,并索引每个部分,获取或设置每个部分。

定义一个属性(property)包括提供属性名称。索引器定义的时候不带有名称,但带有 this 关键字,它指向对象实例。

using System;
namespace IndexerApplication
{
   class IndexedNames
   {
      private string[] namelist = new string[size];
      static public int size = 10;
      public IndexedNames()
      {//在下面 main 方法中 new 的时候,运行此构造方法,所以目前namelist中是10个"N. A."
         for (int i = 0; i < size; i++)
         namelist[i] = "N. A.";
      }
      public string this[int index]//根据 index 索引值 获取内容
      {
         get
         {
            string tmp;

            if( index >= 0 && index <= size-1 )
            {
               tmp = namelist[index];
            }
            else
            {
               tmp = "";
            }

            return ( tmp );
         }
         set
         {
            if( index >= 0 && index <= size-1 )
            {
               namelist[index] = value;//传入的 value 如 Zara
            }
         }
      }
     //重载索引器(Indexer)
//索引器(Indexer)可被重载。索引器声明的时候也可带有多个参数,且每个参数可以是不同的类型。
     //没有必要让索引器必须是整型的。C# 允许索引器可以是其他类型,例如,字符串类型。
 public int this[string name]
      {
         get
         {
            int index = 0;
            while(index < size)
            {
               if (namelist[index] == name)
               {
                return index;
               }
               index++;
            }
            return index;
         }

      }
      static void Main(string[] args)
      {
         IndexedNames names = new IndexedNames();
        //将原本是10个"N. A."的namelist 的前七个进行修改
         names[0] = "Zara";
         names[1] = "Riz";
         names[2] = "Nuha";
         names[3] = "Asif";
         names[4] = "Davinder";
         names[5] = "Sunil";
         names[6] = "Rubic";
         for ( int i = 0; i < IndexedNames.size; i++ )
         {
            Console.WriteLine(names[i]);
         }
        // 使用带有 string 参数的第二个索引器
         Console.WriteLine(names["Nuha"]);
        
         Console.ReadKey();
      }
   }
}

运行结果:

Zara
Riz
Nuha
Asif
Davinder
Sunil
Rubic
N. A.
N. A.
N. A.
2

委托

C# 中的委托(Delegate)类似于 C 或 C++ 中函数的指针。

⚠️ 委托(Delegate) 是存有对某个方法的引用的一种引用类型变量。

引用可在运行时被改变。

委托(Delegate)特别用于实现事件和回调方法。所有的委托(Delegate)都派生自 System.Delegate 类。

1、声明委托(Delegate)

委托声明决定了可由该委托引用的方法。委托可指向一个与其具有相同标签的方法。

//例如,假设有一个委托:
public delegate int MyDelegate (string s);

上面的委托可被用于引用任何一个带有一个单一的 string 参数的方法,并返回一个 int 类型变量。

//声明委托的语法如下:
delegate <return type> <delegate-name> <parameter list>

2、实例化委托(Delegate)

一旦声明了委托类型,委托对象必须使用 new 关键字来创建,且与一个特定的方法有关。当创建委托时,传递到 new 语句的参数就像方法调用一样书写,但是不带有参数。例如:

using System;

delegate int NumberChanger(int n);
namespace DelegateAppl
{
   class TestDelegate
   {
      static int num = 10;
      public static int AddNum(int p)
      {
         num += p;
         return num;
      }

      public static int MultNum(int q)
      {
         num *= q;
         return num;
      }
      public static int getNum()
      {
         return num;
      }

      static void Main(string[] args)
      {
         // 创建委托实例
         NumberChanger nc1 = new NumberChanger(AddNum);
         NumberChanger nc2 = new NumberChanger(MultNum);
         // 使用委托对象调用方法
         nc1(25);
         Console.WriteLine("Value of Num: {0}", getNum());
         nc2(5);
         Console.WriteLine("Value of Num: {0}", getNum());
         Console.ReadKey();
      }
   }
}
//结果:
Value of Num: 35
Value of Num: 175

委托的多播(Multicasting of a Delegate)

委托对象可使用 “+” 运算符进行合并。一个合并委托调用它所合并的两个委托。只有相同类型的委托可被合并。“-” 运算符可用于从合并的委托中移除组件委托。

使用委托的这个有用的特点,您可以创建一个委托被调用时要调用的方法的调用列表。这被称为委托的 多播(multicasting),也叫组播。

using System;

delegate int NumberChanger(int n);
namespace DelegateAppl
{
   class TestDelegate
   {
      static int num = 10;
      public static int AddNum(int p)
      {
         num += p;
         return num;
      }

      public static int MultNum(int q)
      {
         num *= q;
         return num;
      }
      public static int getNum()
      {
         return num;
      }

      static void Main(string[] args)
      {
         // 创建委托实例
         NumberChanger nc;
         NumberChanger nc1 = new NumberChanger(AddNum);
         NumberChanger nc2 = new NumberChanger(MultNum);
         nc = nc1;
         nc += nc2;
         // 调用多播
         nc(5);
         Console.WriteLine("Value of Num: {0}", getNum());
         Console.ReadKey();
      }
   }
}
//结果:
Value of Num: 75

委托(Delegate)的用途

委托 printString 可用于引用带有一个字符串作为输入的方法,并不返回任何东西。

我们使用这个委托来调用两个方法,第一个把字符串打印到控制台,第二个把字符串打印到文件:

using System;
using System.IO;

namespace DelegateAppl
{
   class PrintString
   {
      static FileStream fs;
      static StreamWriter sw;
      // 委托声明
      public delegate void printString(string s);

      // 该方法打印到控制台
      public static void WriteToScreen(string str)
      {
         Console.WriteLine("The String is: {0}", str);
      }
      // 该方法打印到文件
      public static void WriteToFile(string s)
      {
         fs = new FileStream("c:\\message.txt", FileMode.Append, FileAccess.Write);
         sw = new StreamWriter(fs);
         sw.WriteLine(s);
         sw.Flush();
         sw.Close();
         fs.Close();
      }
      // 该方法把委托作为参数,并使用它调用方法
      public static void sendString(printString ps)
      {
         ps("Hello World");
      }
      static void Main(string[] args)
      {
         printString ps1 = new printString(WriteToScreen);
         printString ps2 = new printString(WriteToFile);
         sendString(ps1);
         sendString(ps2);
         Console.ReadKey();
      }
   }
}
//结果:
The String is: Hello World

事件(Event)

事件(Event) 基本上说是一个用户操作,如按键、点击、鼠标移动等等,或者是一些提示信息,如系统生成的通知。应用程序需要在事件发生时响应事件。例如,中断。

C# 中使用事件机制实现线程间的通信。


通过事件使用委托

事件在类中声明且生成,且通过使用同一个类或其他类中的委托与事件处理程序关联。包含事件的类用于发布事件。这被称为 发布器(publisher) 类。其他接受该事件的类被称为 订阅器(subscriber) 类。事件使用 发布-订阅(publisher-subscriber) 模型。

发布器(publisher) 是一个包含事件和委托定义的对象。事件和委托之间的联系也定义在这个对象中。发布器(publisher)类的对象调用这个事件,并通知其他的对象。

订阅器(subscriber) 是一个接受事件并提供事件处理程序的对象。在发布器(publisher)类中的委托调用订阅器(subscriber)类中的方法(事件处理程序)。


1、声明事件(Event)

在类的内部声明事件,首先必须声明该事件的委托类型。例如:

public delegate void BoilerLogHandler(string status);

然后,声明事件本身,使用 event 关键字:

// 基于上面的委托定义事件
public event BoilerLogHandler BoilerEventLog;

上面的代码定义了一个名为 BoilerLogHandler 的委托和一个名为 BoilerEventLog 的事件,该事件在生成的时候会调用委托。

using System;
namespace SimpleEvent
{
  using System;
  /***********发布器类***********/
  public class EventTest
  {
    private int value;

    public delegate void NumManipulationHandler();


    public event NumManipulationHandler ChangeNum;
    protected virtual void OnNumChanged()
    {
      if ( ChangeNum != null )
      {
        ChangeNum(); /* 事件被触发 */
      }else {
        Console.WriteLine( "event not fire" );
        Console.ReadKey(); /* 回车继续 */
      }
    }


    public EventTest()
    {
      int n = 5;
      SetValue( n );
    }


    public void SetValue( int n )
    {
      if ( value != n )
      {
        value = n;
        OnNumChanged();
      }
    }
  }


  /***********订阅器类***********/

  public class subscribEvent
  {
    public void printf()
    {
      Console.WriteLine( "event fire" );
      Console.ReadKey(); /* 回车继续 */
    }
  }

  /***********触发***********/
  public class MainClass
  {
    public static void Main()
    {
      EventTest e = new EventTest(); /* 实例化对象,第一次没有触发事件 */
      subscribEvent v = new subscribEvent(); /* 实例化对象 */
      e.ChangeNum += new EventTest.NumManipulationHandler( v.printf ); /* 注册 */
      e.SetValue( 7 );
      e.SetValue( 11 );
    }
  }
}
//结果:
event not fire
event fire
event fire

一个简单的用于热水锅炉系统故障排除的应用程序。

当维修工程师检查锅炉时,锅炉的温度和压力会随着维修工程师的备注自动记录到日志文件中。

using System;
using System.IO;

namespace BoilerEventAppl
{

   // boiler 类
   class Boiler
   {
      private int temp;
      private int pressure;
      public Boiler(int t, int p)
      {
         temp = t;
         pressure = p;
      }

      public int getTemp()
      {
         return temp;
      }
      public int getPressure()
      {
         return pressure;
      }
   }
   // 事件发布器
   class DelegateBoilerEvent
   {
      public delegate void BoilerLogHandler(string status);

      // 基于上面的委托定义事件
      public event BoilerLogHandler BoilerEventLog;

      public void LogProcess()
      {
         string remarks = "O. K";
         Boiler b = new Boiler(100, 12);
         int t = b.getTemp();
         int p = b.getPressure();
         if(t > 150 || t < 80 || p < 12 || p > 15)
         {
            remarks = "Need Maintenance";
         }
         OnBoilerEventLog("Logging Info:\n");
         OnBoilerEventLog("Temparature " + t + "\nPressure: " + p);
         OnBoilerEventLog("\nMessage: " + remarks);
      }

      protected void OnBoilerEventLog(string message)
      {
         if (BoilerEventLog != null)
         {
            BoilerEventLog(message);
         }
      }
   }
   // 该类保留写入日志文件的条款
   class BoilerInfoLogger
   {
      FileStream fs;
      StreamWriter sw;
      public BoilerInfoLogger(string filename)
      {
         fs = new FileStream(filename, FileMode.Append, FileAccess.Write);
         sw = new StreamWriter(fs);
      }
      public void Logger(string info)
      {
         sw.WriteLine(info);
      }
      public void Close()
      {
         sw.Close();
         fs.Close();
      }
   }
   // 事件订阅器
   public class RecordBoilerInfo
   {
      static void Logger(string info)
      {
         Console.WriteLine(info);
      }//end of Logger

      static void Main(string[] args)
      {
         BoilerInfoLogger filelog = new BoilerInfoLogger("e:\\boiler.txt");
         DelegateBoilerEvent boilerEvent = new DelegateBoilerEvent();
         boilerEvent.BoilerEventLog += new 
         DelegateBoilerEvent.BoilerLogHandler(Logger);
         boilerEvent.BoilerEventLog += new 
         DelegateBoilerEvent.BoilerLogHandler(filelog.Logger);
         boilerEvent.LogProcess();
         Console.ReadLine();
         filelog.Close();
      }//end of main

   }//end of RecordBoilerInfo
}
//结果:
Logging info:

Temperature 100
Pressure 12

Message: O. K

集合(Collection)

集合(Collection)类是专门用于数据存储和检索的类。

这些类提供了对栈(stack)、队列(queue)、列表(list)和哈希表(hash table)的支持。大多数集合类实现了相同的接口。

集合(Collection)类服务于不同的目的,如为元素动态分配内存,基于索引访问列表项等等。这些类创建 Object 类的对象的集合。在 C# 中,Object 类是所有数据类型的基类。

泛型(Generic)

泛型(Generic) 允许延迟编写类或方法中的编程元素的数据类型的规范,直到实际在程序中使用它的时候。换句话说,泛型允许编写一个可以与任何数据类型一起工作的类或方法。

可以通过数据类型的替代参数编写类或方法的规范。当编译器遇到类的构造函数或方法的函数调用时,它会生成代码来处理指定的数据类型。

0、泛型(Generic)的特性

使用泛型是一种增强程序功能的技术,具体表现在以下几个方面:

  • 它有助于您最大限度地重用代码、保护类型的安全以及提高性能。
  • 您可以创建泛型集合类。.NET 框架类库在 System.Collections.Generic 命名空间中包含了一些新的泛型集合类。您可以使用这些泛型集合类来替代 System.Collections 中的集合类。
  • 您可以创建自己的泛型接口、泛型类、泛型方法、泛型事件和泛型委托。
  • 您可以对泛型类进行约束以访问特定数据类型的方法。
  • 关于泛型数据类型中使用的类型的信息可在运行时通过使用反射获取。

1、泛型类

using System;
using System.Collections.Generic;

namespace GenericApplication
{
    public class MyGenericArray<T>
    {
        private T[] array;
        public MyGenericArray(int size)
        {
            array = new T[size + 1];
        }
        public T getItem(int index)
        {
            return array[index];
        }
        public void setItem(int index, T value)
        {
            array[index] = value;
        }
    }
           
    class Tester
    {
        static void Main(string[] args)
        {
            // 声明一个整型数组
            MyGenericArray<int> intArray = new MyGenericArray<int>(5);
            // 设置值
            for (int c = 0; c < 5; c++)
            {
                intArray.setItem(c, c*5);
            }
            // 获取值
            for (int c = 0; c < 5; c++)
            {
                Console.Write(intArray.getItem(c) + " ");
            }
            Console.WriteLine();
            // 声明一个字符数组
            MyGenericArray<char> charArray = new MyGenericArray<char>(5);
            // 设置值
            for (int c = 0; c < 5; c++)
            {
                charArray.setItem(c, (char)(c+97));
            }
            // 获取值
            for (int c = 0; c < 5; c++)
            {
                Console.Write(charArray.getItem(c) + " ");
            }
            Console.WriteLine();
            Console.ReadKey();
        }
    }
}
//结果:
0 5 10 15 20
a b c d e

2、泛型(Generic)方法

在上面的实例中,我们已经使用了泛型类,我们可以通过类型参数声明泛型方法。

using System;
using System.Collections.Generic;

namespace GenericMethodAppl
{
    class Program
    {
        static void Swap<T>(ref T lhs, ref T rhs)
        {
            T temp;
            temp = lhs;
            lhs = rhs;
            rhs = temp;
        }
        static void Main(string[] args)
        {
            int a, b;
            char c, d;
            a = 10;
            b = 20;
            c = 'I';
            d = 'V';

            // 在交换之前显示值
            Console.WriteLine("Int values before calling swap:");
            Console.WriteLine("a = {0}, b = {1}", a, b);
            Console.WriteLine("Char values before calling swap:");
            Console.WriteLine("c = {0}, d = {1}", c, d);

            // 调用 swap
            Swap<int>(ref a, ref b);
            Swap<char>(ref c, ref d);

            // 在交换之后显示值
            Console.WriteLine("Int values after calling swap:");
            Console.WriteLine("a = {0}, b = {1}", a, b);
            Console.WriteLine("Char values after calling swap:");
            Console.WriteLine("c = {0}, d = {1}", c, d);
            Console.ReadKey();
        }
    }
}
//结果:
Int values before calling swap:
a = 10, b = 20
Char values before calling swap:
c = I, d = V
Int values after calling swap:
a = 20, b = 10
Char values after calling swap:
c = V, d = I

3、泛型(Generic)委托

您可以通过类型参数定义泛型委托。例如:

delegate T NumberChanger<T>(T n);

实例:

using System;
using System.Collections.Generic;

delegate T NumberChanger<T>(T n);
namespace GenericDelegateAppl
{
    class TestDelegate
    {
        static int num = 10;
        public static int AddNum(int p)
        {
            num += p;
            return num;
        }

        public static int MultNum(int q)
        {
            num *= q;
            return num;
        }
        public static int getNum()
        {
            return num;
        }

        static void Main(string[] args)
        {
            // 创建委托实例
            NumberChanger<int> nc1 = new NumberChanger<int>(AddNum);
            NumberChanger<int> nc2 = new NumberChanger<int>(MultNum);
            // 使用委托对象调用方法
            nc1(25);
            Console.WriteLine("Value of Num: {0}", getNum());
            nc2(5);
            Console.WriteLine("Value of Num: {0}", getNum());
            Console.ReadKey();
        }
    }
}
//结果:
Value of Num: 35
Value of Num: 175

匿名方法

委托是用于引用与其具有相同标签的方法。换句话说,可以使用委托对象调用可由委托引用的方法。

匿名方法(Anonymous methods) 提供了一种传递代码块作为委托参数的技术。匿名方法是没有名称只有主体的方法。

在匿名方法中不需要指定返回类型,它是从方法主体内的 return 语句推断的。

编写匿名方法的语法:

匿名方法是通过使用 delegate 关键字创建委托实例来声明的。例如:

delegate void NumberChanger(int n);
...
NumberChanger nc = delegate(int x)
{
    Console.WriteLine("Anonymous Method: {0}", x);
};

代码块 Console.WriteLine("Anonymous Method: {0}", x); 是匿名方法的主体。

委托可以通过匿名方法调用,也可以通过命名方法调用,即,通过向委托对象传递方法参数。

注:匿名方法的主体后面需要一个 ;

例如:nc(10);

using System;

delegate void NumberChanger(int n);
namespace DelegateAppl
{
    class TestDelegate
    {
        static int num = 10;
        public static void AddNum(int p)
        {
            num += p;
            Console.WriteLine("Named Method: {0}", num);
        }

        public static void MultNum(int q)
        {
            num *= q;
            Console.WriteLine("Named Method: {0}", num);
        }

        static void Main(string[] args)
        {
            // 使用匿名方法创建委托实例
            NumberChanger nc = delegate(int x)
            {
               Console.WriteLine("Anonymous Method: {0}", x);
            };
            
            // 使用匿名方法调用委托
            nc(10);

            // 使用命名方法实例化委托
            nc =  new NumberChanger(AddNum);
            
            // 使用命名方法调用委托
            nc(5);

            // 使用另一个命名方法实例化委托
            nc =  new NumberChanger(MultNum);
            
            // 使用命名方法调用委托
            nc(2);
            Console.ReadKey();
        }
    }
}
//结果:
Anonymous Method: 10
Named Method: 15
Named Method: 30

不安全代码

当一个代码块使用 unsafe 修饰符标记时,C# 允许在函数中使用指针变量。 不安全代码 或非托管代码是指使用了 指针 变量的代码块。

指针变量:

指针 是值为另一个变量的地址的变量,即,内存位置的直接地址。就像其他变量或常量,您必须在使用指针存储其他变量地址之前声明指针。

指针变量声明的一般形式为:

type* var-name;

using System;
namespace UnsafeCodeApplication
{
    class Program
    {
        static unsafe void Main(string[] args)
        {
            int var = 20;
            int* p = &var;
            Console.WriteLine("Data is: {0} ",  var);
            Console.WriteLine("Address is: {0}",  (int)p);
            Console.ReadKey();
        }
    }
}
//结果:
Data is: 20
Address is: 99215364

也可以不用声明整个方法作为不安全代码,只需要声明方法的一部分作为不安全代码。

1、使用 ToString() 方法检索存储在指针变量所引用位置的数据。

using System;
namespace UnsafeCodeApplication
{
   class Program
   {
      public static void Main()
      {
         unsafe
         {
            int var = 20;
            int* p = &var;
            Console.WriteLine("Data is: {0} " , var);
            Console.WriteLine("Data is: {0} " , p->ToString());
            Console.WriteLine("Address is: {0} " , (int)p);
         }
         Console.ReadKey();
      }
   }
}
//结果:
Data is: 20
Data is: 20
Address is: 77128984

2、传递指针作为方法的参数

using System;
namespace UnsafeCodeApplication
{
   class TestPointer
   {
      public unsafe void swap(int* p, int *q)
      {
         int temp = *p;
         *p = *q;
         *q = temp;
      }

      public unsafe static void Main()
      {
         TestPointer p = new TestPointer();
         int var1 = 10;
         int var2 = 20;
         int* x = &var1;
         int* y = &var2;
         
         Console.WriteLine("Before Swap: var1:{0}, var2: {1}", var1, var2);
         p.swap(x, y);

         Console.WriteLine("After Swap: var1:{0}, var2: {1}", var1, var2);
         Console.ReadKey();
      }
   }
}
//结果:
Before Swap: var1: 10, var2: 20
After Swap: var1: 20, var2: 10

3、使用指针访问数组元素

在 C# 中,数组名称和一个指向与数组数据具有相同数据类型的指针是不同的变量类型。例如,int *p 和 int[] p 是不同的类型。

可以增加指针变量 p,因为它在内存中不是固定的,但是数组地址在内存中是固定的,所以不能增加数组 p。

因此,如果需要使用指针变量访问数组数据,可以像我们通常在 C 或 C++ 中所做的那样,使用 fixed 关键字来固定指针。

using System;
namespace UnsafeCodeApplication
{
   class TestPointer
   {
      public unsafe static void Main()
      {
         int[]  list = {10, 100, 200};
         fixed(int *ptr = list)

         /* 显示指针中数组地址 */
         for ( int i = 0; i < 3; i++)
         {
            Console.WriteLine("Address of list[{0}]={1}",i,(int)(ptr + i));
            Console.WriteLine("Value of list[{0}]={1}", i, *(ptr + i));
         }
         Console.ReadKey();
      }
   }
}
//结果:
Address of list[0] = 31627168
Value of list[0] = 10
Address of list[1] = 31627172
Value of list[1] = 100
Address of list[2] = 31627176
Value of list[2] = 200

4、编译不安全代码

为了编译不安全代码,您必须切换到命令行编译器指定 /unsafe 命令行。

例如,为了编译包含不安全代码的名为 prog1.cs 的程序,需在命令行中输入命令:

csc /unsafe prog1.cs

如果您使用的是 Visual Studio IDE,那么您需要在项目属性中启用不安全代码。

步骤如下:

  • 通过双击资源管理器(Solution Explorer)中的属性(properties)节点,打开项目属性(project properties)
  • 点击 Build 标签页。
  • 选择选项"Allow unsafe code"。

多线程

线程 被定义为程序的执行路径。每个线程都定义了一个独特的控制流。如果应用程序涉及到复杂的和耗时的操作,那么设置不同的线程执行路径往往是有益的,每个线程执行特定的工作。

线程是 轻量级进程 。

一个使用线程的常见实例是现代操作系统中并行编程的实现。使用线程节省了 CPU 周期的浪费,同时提高了应用程序的效率。

到目前为止我们编写的程序是一个单线程作为应用程序的运行实例的单一的过程运行的。但是,这样子应用程序同时只能执行一个任务。为了同时执行多个任务,它可以被划分为更小的线程。


线程生命周期:

线程生命周期开始于 System.Threading.Thread 类的对象被创建时,结束于线程被终止或完成执行时。

下面列出了线程生命周期中的各种状态:

  • 未启动状态:当线程实例被创建但 Start 方法未被调用时的状况。
  • 就绪状态:当线程准备好运行并等待 CPU 周期时的状况。
  • 不可运行状态,下面的几种情况下线程是不可运行的:
    • 已经调用 Sleep 方法
    • 已经调用 Wait 方法
    • 通过 I/O 操作阻塞
  • 死亡状态:当线程已完成执行或已中止时的状况。

C#代码审计

实际只是进行一个复审,但也记录一下哈哈哈哈哈

Regex 的 replace方法

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
 
 
namespace _04字符替换
{
    class Program
    {
        static void Main(string[] args)
        {
            #region 字符串替换
            /*
             使用正则替换字符串
             */
            string msg = "你aaa好aa哈哈a";
          
          //重点是这里
            msg = Regex.Replace(msg, @"a+","A");
          
            Console.WriteLine(msg);
 
            /*
             替换中使用提取组
            在替换中也是可以使用提取组的 $表示提取组 $1表示组1  (引用正则表达式提取的字符串)
            */
            string msg2 = "Hello 'welcome' to 'china'";
            
            msg2 = Regex.Replace(msg2, "'(.+?)'", "[$1]");
            Console.WriteLine(msg2);
 
            /*
             隐藏手机号码
             */
            string msg3 = "文天祥12345678911";
            msg3 = Regex.Replace(msg3, "([0-9]{4})[0-9]{4}([0-9]{3})", "$1****$2");
 
            Console.WriteLine(msg3);
 
            /*
             隐藏邮箱名
             */
            string msg4 = "123456789@qq.com";
            msg4 = Regex.Replace(msg4, "(.+?)@", "*****");
            Console.WriteLine(msg4);
 
 
            #endregion
 
            Console.ReadKey();
        }
    }
}

replace 可以使用在xss防御上

另外也搜到了过滤器相关文章

split

Split参数StringSplitOptions.RemoveEmptyEntries的使用(去除空数据):https://blog.csdn.net/u010178308/article/details/76847915

string test = "程$晓$";

使用:

 string[] temp = test.Split(new string[] { "$" }, StringSplitOptions.RemoveEmptyEntries);

输出结果:数组长度为2 temp[0]=“程” temp[1]=“晓”;

使用:

 string[] temp = test.Split(new string[] { "$" }, StringSplitOptions.None);string[] temp = test.Split('$');

输出结果:数组长度为3 temp[0]=“程” temp[1]=“晓” temp[2]=“”;

DateTime.Now.Ticks

原文链接:https://blog.csdn.net/lishiyuzuji/article/details/7087214

时间换算:

1秒=1000毫秒;

1毫秒=1000微秒;

1微秒=1纳秒

而1毫秒=10000ticks;所以1ticks=100纳秒=0.1微秒

ticks这个属性值是指从0001年1月1日12:00:00开始到此时的以ticks为单位的时间,就是以ticks表示的时间的间隔数。

使用DateTime.Now.Ticks返回的是一个long型的数值。

C#项目中一些文件类型说明

参考

designer.cs:是窗体设计器生成的代码文件,作用是对窗体上的控件做初始化工作(在函数InitializeComponent()中)VS 2003以前都把这部分代码放到窗体的cs文件中,由于这部分代码一般不用手工修改,在VS 2005以后把它单独分离出来形成一个designer.cs文件与窗体对应。这样cs文件中剩下的代码都是与程序功能相关性较高的代码利于维护。

.sln:解决方案文件,为解决方案资源管理器提供显示管理文件的图形接口所需的信息。

.csproj:项目文件,创建应用程序所需的引用、数据连接、文件夹和文件的信息。

.aspx:Web 窗体页由两部分组成:视觉元素(HTML、服务器控件和静态文本)和该页的编程逻辑。Visual Studio 将这两个组成部分分别存储在一个单独的文件中。视觉元素在.aspx 文件中创建。

.ascx:ASP.NET 的用户控件(也叫做“pagelets”),是作为一种封装了特定功能和行为(这两者要被用在Web应用程序的各种页面上)的Web页面被开发的。一个用 户控件包含了HTML、代码和其他Web或者用户控件的组合,并在Web服务器上以自己的文件格式保存,其扩展名是*.ascx。ASP.NET里的缺省 配置并不允许Web客户端通过URL来访问这些文件,但是这个网站的其他页面可以集成这些文件里所包含的功能。

.aspx.cs:Web 窗体页的编程逻辑位于一个单独的类文件中,该文件称作代码隐藏类文件(.aspx.cs)。

.cs: 类模块代码文件。业务逻辑处理层的代码。

.asax:Global.asax 文件(也叫做 ASP.NET 应用程序文件)是一个可选的文件,该文件包含响应 ASP.NET 或 HTTP 模块引发的应用程序级别事件的代码。

.config:Web.config 文件向它们所在的目录和所有子目录提供配置信息。

.aspx.resx/.resx:资源文件,资源是在逻辑上由应用程序部署的任何非可执行数据。通过在资源文件中存储数据,无需重新编译整个应用程序即可更改数据。

.XSD:XML schema的一种.从DTD,XDR发展到XSD

.pdb:PDB(程序数据库)文件保持着调试和项目状态信息,从而可以对程序的调试配置进行增量链接。

.suo:解决方案用户选项,记录所有将与解决方案建立关联的选项,以便在每次打开时,它都包含您所做的自定义设置。

.asmx:asmx 文件包含 WebService 处理指令,并用作 XML Web services 的可寻址入口点

.vsdisco(项目发现)文件 基于 XML 的文件,它包含为 Web 服务提供发现信息的资源的链接 (URL)。

.htc:一个HTML文件,包含脚本和定义组件的一系列HTC特定元素.htc提供在脚本中implement组件的机制

C#加Vue MVC+Vue快速开发

https://blog.csdn.net/weixin_43942765/article/details/121938973

1、创建新项目【ASP.NET Core Web 应用程序】

创建后的样子:

2、配置Startup.cs

1.添加服务器端缓存
2.使用服务器端缓存
3.修改启动项为Home控制器下的Home视图

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Supply_System
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddResponseCaching();//1.添加服务器端缓存
            services.AddControllersWithViews();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }
            app.UseHttpsRedirection();
            app.UseStaticFiles();

            app.UseRouting();

            app.UseAuthorization();

            app.UseResponseCaching();//2.使用服务器端缓存

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Home}/{id?}");//3.修改启动项为Home控制器下的Home视图
            });
        }
    }
}
//ps:我的案例里没有第 1,2 步服务器缓存那些事耶

2、在Models文件夹下添加两个实体类

1.SupplyDemandsViewModel
2.TypeViewModel

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Supply_System.Models
{
    public class SupplyDemandsViewModel
    {
        public int Id { get; set; }
        public string SupplyDemandTitle { get; set; }
        public string SupplyDemandDetail { get; set; }
        public string CreateTime { get; set; }
        public int CreateUserId { get; set; }
        public int TypeId { get; set; }
        public string TypeName { get; set; }
        public bool IsRecommend { get; set; }
        public bool IsDel { get; set; }
        public static List<SupplyDemandsViewModel> ListAll()
        {
            List<SupplyDemandsViewModel> supplyDemands = new List<SupplyDemandsViewModel>();
            for (int i = 0; i < 8; i++)
            {
                bool IsRecommend = false;
                if (i > 4)
                {
                    IsRecommend = true;
                }
                supplyDemands.Add(new SupplyDemandsViewModel
                {
                    Id = 1,
                    SupplyDemandTitle = "v-if",
                    SupplyDemandDetail = "",
                    CreateTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
                    CreateUserId = 1,
                    TypeId = 1,
                    TypeName = "Vue控件",
                    IsRecommend = IsRecommend,
                    IsDel = false
                });
            }
            for (int i = 0; i < 8; i++)
            {
                bool IsRecommend = false;
                if (i > 4)
                {
                    IsRecommend = true;
                }
                supplyDemands.Add(new SupplyDemandsViewModel
                {
                    Id = 1,
                    SupplyDemandTitle = "vm",
                    SupplyDemandDetail = "",
                    CreateTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
                    CreateUserId = 1,
                    TypeId = 2,
                    TypeName = "Vue语法",
                    IsRecommend = IsRecommend,
                    IsDel = false
                });
            }
            for (int i = 0; i < 8; i++)
            {
                bool IsRecommend = false;
                if (i > 4)
                {
                    IsRecommend = true;
                }
                supplyDemands.Add(new SupplyDemandsViewModel
                {
                    Id = 1,
                    SupplyDemandTitle = "商城",
                    SupplyDemandDetail = "",
                    CreateTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
                    CreateUserId = 1,
                    TypeId = 3,
                    TypeName = "Vue实战",
                    IsRecommend = IsRecommend,
                    IsDel = false
                });
            }
            return supplyDemands;
        }
    }
    
}


using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Supply_System.Models
{
    public class TypeViewModel
    {
        public int Id { get; set; }
        public string TypeName { get; set; }
        public string Url { get; set; }
        public static List<TypeViewModel> ListAll()
        {
            List<TypeViewModel> navViewNodels = new List<TypeViewModel>();
            navViewNodels.Add(new TypeViewModel
            {
                Id = 1,
                TypeName = "Vue控件",
                Url = ""
            });
            navViewNodels.Add(new TypeViewModel
            {
                Id = 2,
                TypeName = "Vue语法",
                Url = ""
            });
            navViewNodels.Add(new TypeViewModel
            {
                Id = 3,
                TypeName = "Vue实战",
                Url = ""
            });
            return navViewNodels;
        }
    }
}

3、修改HomeController控制器

1.添加浏览器端缓存
2.加载栏目数据
3.加载栏目对应的内容数据

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Supply_System.Models;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;

namespace Supply_System.Controllers
{
    public class HomeController : Controller
    {
        private readonly ILogger<HomeController> _logger;

        public HomeController(ILogger<HomeController> logger)
        {
            _logger = logger;
        }

        public IActionResult Index()
        {
            return View();
        }
        [ResponseCache(Duration = 600)]/*1.浏览器端缓存*/
        public IActionResult Home()
        {
            return View();
        }
        [ResponseCache(Duration = 600)]
        public IActionResult GetNavs()/*2.加载栏目数据*/
        {
            return new JsonResult(TypeViewModel.ListAll());
        }
        [ResponseCache(Duration = 600)]
        public IActionResult GetSuppys ()/*3.加载栏目对应的内容数据*/
        {
            return new JsonResult(SupplyDemandsViewModel.ListAll());
        }

        public IActionResult Privacy()
        {
            return View();
        }

        [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
        public IActionResult Error()
        {
            return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
        }
    }
}

页面啥的,懒得写了,看原文吧

C#+Vue实现文件的前后端传输

参考文章

.NET仓储设计模式

https://blog.csdn.net/weixin_30315435/article/details/98642956

仓储(Respository)是存在于工作单元和数据库之间单独分离出来的一层,是对数据访问的封装。其优点:

1)业务层不需要知道它的具体实现,达到了分离关注点。

2)提高了对数据库访问的维护,对于仓储的改变并不会改变业务的逻辑,数据库可以用Sql Server(该系列博客使用)、MySql等。

JSON.parse()和JSON.stringify()用法解析

https://blog.csdn.net/MaNong_girl/article/details/120485741

1、JSON.stringify() 从一个对象中解析出字符串

JSON.stringify({"a":"1","b":"2"})

结果是:"{"a":"1","b":"2"}"

2、JSON.parse() 从一个字符串中解析出JSON对象

var str = '{"a":"1","b":"2"}';

JSON.parse(str);

结果是:Object{a:"1",b:"2"}

ps:我的案例基本上是这样式的:xxx = JSON.parse(JSON.stringify(data)); 所以说是一个对象变成了字符串,又变成了json对象嘛hhhh

Guid

GUID(全局统一标识符)是指在一台机器上生成的数字,它保证对在同一时空中的所有机器都是唯一的。通常平台会提供生成GUID的API。生成算法很有意思,用到了以太网卡地址、纳秒级时间、芯片ID码和许多可能的数字。GUID的唯一缺陷在于生成的结果串会比较大。”

  1. 一个GUID为一个128位的整数(16字节),在使用唯一标识符的情况下,你可以在所有计算机和网络之间使用这一整数。

  2. GUID 的格式为“xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx”,其中每个 x 是 0-9 或 a-f 范围内的一个十六进制的数字。例如:337c7f2b-7a34-4f50-9141-bab9e6478cc8 即为有效的 GUID 值。

  3. 世界上(Koffer注:应该是地球上)的任何两台计算机都不会生成重复的 GUID 值。GUID 主要用于在拥有多个节点、多台计算机的网络或系统中,分配必须具有唯一性的标识符。

  4. 在 Windows 平台上,GUID 应用非常广泛:注册表、类及接口标识、数据库、甚至自动生成的机器名、目录名等。

**.NET中使用GUID **

GUID 在 .NET 中使用非常广泛,而且 .NET Framework 提供了专门 Guid 基础结构。

Guid 结构的常用法包括:

  1. Guid.NewGUID()
    生成一个新的 GUID 唯一值
  2. Guid.ToString()
    将 GUID 值转换成字符串,便于处理
  3. 构造函数 Guid(string)
    由 string 生成 Guid 结构,其中string 可以为大写,也可以为小写,可以包含两端的定界符“{}”或“()”,甚至可以省略中间的“-”,Guid 结构的构造函数有很多,其它构造用法并不常用。
using System;

namespace TestGuid
{
    class Program
    {
        static void Main(string[] args)
        {
            // format 参数可以是“N”、“D”、“B”、“P”或“X”。如果 format为 null 或空字符串 (""),则使用“D”。
            string uuid = Guid.NewGuid().ToString();
            string uuid2 = Guid.NewGuid().ToString("N");
            string uuid3 = Guid.NewGuid().ToString("D");
            string uuid4 = Guid.NewGuid().ToString("B");
            string uuid5 = Guid.NewGuid().ToString("P");
            string uuid6 = Guid.NewGuid().ToString("x");

            Console.WriteLine(uuid);  // 64bc127b-4339-4121-b626-cdb8dc58f517
            Console.WriteLine(uuid2);  // ebb60bc3b1554a4b809141212428a7d8
            Console.WriteLine(uuid3);  // 00155eb3-3a7e-4097-b404-dc00a035ddad
            Console.WriteLine(uuid4);  // {a9101c4b-bb1e-4491-b40b-8d7872c9c7df}
            Console.WriteLine(uuid5);  // (de66cc8b-32eb-466d-81ef-ecb87e2e1431)
            Console.WriteLine(uuid6);  // {0x7cbf8638,0x39cb,0x495d,{0x80,0x34,0x2d,0xbe,0x75,0xb6,0xad,0x87}}

            Console.ReadLine();
        }
    }
}
说明符说明格式
N32 十六进制数字00000000000000000000000000000000
D用连字符分隔的 32 个十六进制数字00000000-0000-0000-0000-000000000000
B用连字符分隔的 32 个十六进制数字,用大括号括起来{00000000-0000-0000-0000-000000000000}
P用连字符分隔的 32 个十六进制数字,括在括号中(00000000-0000-0000-0000-000000000000)
X四个十六进制值括在大括号中,其中第四个值是八个十六进制值的子集,也括在大括号中{0x00000000,0x0000,0x0000,{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}}

1、Guid.Parse

https://docs.microsoft.com/zh-cn/dotnet/api/system.guid.parse?view=net-6.0

Parse(ReadOnlySpan)将表示 GUID 的只读字符范围转换为等效的 Guid 结构。
Parse(String)将 GUID 的字符串表示形式转换为等效的 Guid 结构。

2、Guid.Empty

Guid 结构的只读实例,其值均为零。

可以将 GUID 与字段的值 Guid.Empty 进行比较,以确定 GUID 是否为非零。 以下示例使用 Equality运算符比较两个 GUID 值,以确定 Guid.Empty 它们是否只包含零。

// Create a GUID and determine whether it consists of all zeros.
Guid guid1 = Guid.NewGuid();
Console.WriteLine(guid1);
Console.WriteLine($"Empty: {guid1 == Guid.Empty}\n");

// Create a GUID with all zeros and compare it to Empty.
var bytes = new Byte[16];
var guid2 = new Guid(bytes);
Console.WriteLine(guid2);
Console.WriteLine($"Empty: {guid2 == Guid.Empty}");

// The example displays output like the following:
//       11c43ee8-b9d3-4e51-b73f-bd9dda66e29c
//       Empty: False
//
//       00000000-0000-0000-0000-000000000000
//       Empty: True

NonAction

[NonAction]:该特性用于指示控制器方法不是操作方法。

NonAction表示它不是一个真正的Action,而是一个普通方法。

默认情况下,MVC 框架将 controller 类的所有公共方法都视为操作方法Action(浏览器输入地址即可访问)。

如果ni的 controller 类包含公共方法,并且您不希望它成为操作方法Aciton,则必须用 NonActionAttribute 特性标记该方法。

NonActionAttribute 类

表示一个特性,该特性用于指示控制器方法不是操作方法。在其他控制器中可调用该方法

[NonAction]
        public string GetClassName()
        {
            return "hello";
        }//controller 中直接调用GetClassName()

另外还有 ChildActionOnly 类

ChildActionOnly表示它只能在View中通过Html.Action或Html.RenderAction来使用,不能被 Controller 直接调用, 一般返回的是局部视图,例如更新局部页面时,在 主view 中使用 Html.Action 或 Html.RenderAction 来调用

[ChildActionOnly]
        public ActionResult GetClassName(int id)
        {
            return Content("体育");
        }//view中直接调用@Html.Action("GetClassName", new { id = 1 })

EntityFramework

EF 框架 是微软的.NET中ORM(对象关系映射)框架。

在EF框架中把数据库中的关系表对应到了程序中的实体类,把数据表中的字段对应成了实体类中的属性,这就是对象关系映射

原文链接:https://blog.csdn.net/lnazj/article/details/79062652

架构组件:

EDM(实体数据模型): EDM由三个主要部分组成 - 概念模型,映射和存储模型。
Conceptual Model:概念模型包含模型类及其关系。这将独立于您的数据库表设计。
Storage Model:存储模型是包括表,视图,存储过程及其关系和密钥的数据库设计模型。
Mapping:映射由有关概念模型如何映射到存储模型的信息组成。
LINQ to Entities: LINQ to Entities是一种用于针对对象模型编写查询的查询语言。它返回在概念模型中定义的实体。你可以在这里使用你的LINQ技能。
Entity SQL:实体SQL是另一种查询语言(仅适用于EF 6),就像LINQ to Entities一样。然而,这比L2E稍微难一些,开发者需要单独学习。
ObjectServices:对象服务是访问数据库中的数据并将其返回的主要入口点。对象服务负责实现,这是将从实体客户端数据提供者(下一层)返回的数据转换为实体对象结构的过程。
Entity Client Data Provider:此层的主要职责是将LINQ-to-Entities或实体SQL查询转换为底层数据库可以理解的SQL查询。它与ADO.Net数据提供者通信,而ADO.Net数据提供者又从数据库发送或检索数据。
ADO.Net Data Provider:该层使用标准的ADO.Net与数据库进行通信。

功能:

  • 将实体类映射到数据库模式
  • 将linq 查询翻译并执行到sql
  • 跟踪实体在其生命周期内发生的变化,并将更改保存到数据库。
EF 的使用

https://blog.csdn.net/csdn2990/article/details/121281103

一、先安装对应的数据库映射工具。一般对应的数据库映射工具包含EntityFrameworkCore的依赖。

  1. 一般EntityFrameworkCore(ORM映射工具)安装在你的数据项目工程中,这里我通过nuget安装的sqlserver的包。对应的数据和安装对应的包就行。包名如下:Microsoft.EntityFrameworkCore.SqlServer

  2. 然后我们建立好对应的数据Model

  3. Model建立完成后,我们建立数据库上下文文件如下

在这里插入图片描述

该类需要继承DbContext。跟数据库的交互,都在这个类中进行。

其中 DbSet 的作用就是暴露Modle让他们能够在该类中正常使用。DbSet中的泛型相当于数据库中的表。

OnConfiguring可以做一些相关的配置,比如链接字符串。

二、安装进行数据迁移的命令工具。

  1. Model建立好之后,我们可以安装对应的工具,进行数据迁移。安装如下包:Microsoft.EntityFrameworkCore.Tools,同样该包也是安装在你的数据工程当中。

  2. 打开包管理控制台

  3. 进行数据迁移,

    先把默认项目选为你安装了包的项目

然后执行: get-help entityframework 获取所有的指令,一般用添加指令和更新指令就够了。

然后我们进行第一次数据迁移,输入add-migration Initial的指令,其中inital为迁移的名称(会在生成的文件名中的_后面出现,也会是生成文件的类名。ps:类名可以和文件名不统一),可以随便取。

会有如同的提示,大概意思就是你的启动项没有安装 Microsoft.EntityFrameworkCore.Design这个包,然后你去把这个包安装到你的启动项中,然后再执行一遍刚才的命令。

  1. 分析生成的文件。
    当我们执行数据迁移成功后,会生成如图文件:
在这里插入图片描述

其中DemoContextModelSnapshot.cs它相当于是个快照,它非常重要,而且我们不能手动去修改,作用就是EFcore通过他去追踪我们Model的一个状态,比如说:我们又加了一个model,EFcore就会读取这个快照,看看他当前所追踪的快照是什么样,然后跟他以现在大model进行比较,比较出差异之后,就知道改一些什么东西了。

看下Initial这个类
在这里插入图片描述

这个类继承了Migration这个类,相当于使用了他的API,这Initial类中就2个方法。

  • UP方法的作用是:对数据库做出的修改(里面的具体内容比较简单,自己可以看看)
  • Down方法:当修改有些问题的时候,我们要进行一个回滚,他就执行这个Down方法。

执行生成数据库或者生成脚本

执行命令来生成sql脚本(一般生产环境下才会使用):Script-Migration

开发环境一般使用一下命令进行数据库更新:update-database -verbose
其中verbose是可选参数,加了之后可以看到数据库生成的详细信息。一般不加。

执行完成后可以看到如图 数据库 中生成了4张对应的表,(os:还有个Players在DBSet那里没有截图住,这里也没截住)

其中三张是我自己创建的Model,另外__EFMigrationsHistory这个表是用来做数据迁移的历史纪录的。

另外上面我们采用的方式是code first方式,还有其他方式,详情请看微软文档。EF core官网
同样如果有写好的数据库脚本,也可以通过数据库脚本去生成对应的模型。详情请看微软文档。

三、接着我们来对模型中的字段做一些相应的限制。

首先打开nuget安装system.componentModel.Annotations相应的库

然后我们进行相关的引用:using System.ComponentModel.DataAnnotations;

然后我们就可以在相应的字段中进行注释。下图设置了一些常用注释,详细注释请参考微软官网

在这里插入图片描述

因为做了相应的更改,所以我们可以进行一次数据迁移。执行如下命令,进行更新操作:Add-migration ChangeSomeOrioerties

执行成功后,我们可以看下执行后生成的对应文件。(再次生成那个带up,down方法的文件)

using System;
using Microsoft.EntityFrameworkCore.Migrations;

namespace Demo.Data.Migrations
{
    public partial class ChangeSomeOrioerties : Migration
    {
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.AlterColumn<string>(
                name: "Name",
                table: "Leagues",
                type: "nvarchar(100)",
                maxLength: 100,
                nullable: true,
                oldClrType: typeof(string),
                oldType: "nvarchar(max)",
                oldNullable: true);

            migrationBuilder.AlterColumn<string>(
                name: "Country",
                table: "Leagues",
                type: "nvarchar(50)",
                maxLength: 50,
                nullable: false,
                defaultValue: "",
                oldClrType: typeof(string),
                oldType: "nvarchar(max)",
                oldNullable: true);

            migrationBuilder.AlterColumn<DateTime>(
                name: "DateOfEstablishment",
                table: "Clubs",
                type: "date",
                nullable: false,
                oldClrType: typeof(DateTime),
                oldType: "datetime2");
        }

        protected override void Down(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.AlterColumn<string>(
                name: "Name",
                table: "Leagues",
                type: "nvarchar(max)",
                nullable: true,
                oldClrType: typeof(string),
                oldType: "nvarchar(100)",
                oldMaxLength: 100,
                oldNullable: true);

            migrationBuilder.AlterColumn<string>(
                name: "Country",
                table: "Leagues",
                type: "nvarchar(max)",
                nullable: true,
                oldClrType: typeof(string),
                oldType: "nvarchar(50)",
                oldMaxLength: 50);

            migrationBuilder.AlterColumn<DateTime>(
                name: "DateOfEstablishment",
                table: "Clubs",
                type: "datetime2",
                nullable: false,
                oldClrType: typeof(DateTime),
                oldType: "date");
        }
    }
}

这个文件是如何生成出来的呢,EF Core会先和之前的 DemoContextModelSnapshot.cs 也就是 **快照 **这个类进行对比,对比后EFcore就可以算出来你又做了哪些动作,然后达到状态同步的一个效果。

然后我们执行命令对数据库进行更新操作:update-database

然后我们可以看下迁移表:

在这里插入图片描述

四、接着我们来对模型进行一对多,多对多,一对一的关系建立。

有如下关系:

这个文章写得不错,下面还很多内容(增删改查),看原文吧

因为和下面Dbcontext的内容有些重复就不写了,但我觉得这个文章写的更容易理解些

举个查询的小例子:

//精确查询:   
            // 方式1 Linq方法进行查询
            var leagues = context.Leagues.Where(x => x.Country == "Italy").ToList();

            // 方式2  Linq语句的方式进行查询
            var leagues2 = (from lg in context.Leagues
                            where lg.Country == "Italy" 
                            select lg).ToList();

//模糊查询:
//方式1:
var leagues = context.Leagues.Where(x => x.Country.Contains("a")).ToList();
//方式2:
   var leagues1 = context.Leagues.Where(x => 
            EF.Functions.Like(x.Country,"%a%")
            ).ToList();

Where之后返回的是IQueryable类型 此时只生成了对应的sql语句,而并没有执行,它会延迟加载 只有你ToList之后才会立即执行。

这里举例一下和ToList有等价效果的函数,这些函数都会使sql立即执行。

Tolist() 返回查询结果。
First() 返回第一条数据,没有数据会报错。
FirstOrDefault() 返回第一条数据 它可以有返回结果,也可以没有
Single() 符合查询条件的只能是一条数据
SingleOrDefault() 符合查询条件的只能是一条数据,或者没有数据
Last() 返回最后一条
LastOrDefault() 返回最后一条,可以为空
Count() 返回查询条数
LongCount() 返回表示序列中满足条件的元素的数量的
Min() 最小值
Max() 最大值
Sum() 计算总和
Average() 平均值
Find() 查找匹配的结果,这个不属于Linq方法,是Contex的方法,但也会立即执行

以上方法都有对应的异步方法。

----还有什么加载之类的东西,懒得写了

DbContext

参考文章

DbContext类是EntityFramework (简称 EF)中的一个类,可以理解为一个数据库对象的实例。在 EF中,无需手动的拼接 SQL 语句对数据库进行增删改查,而是通过 DbContext 来进行相应操作。

DbContext类是实体框架的重要组成部分,是域或实体类与数据库之间的桥梁。

DbContext是负责与数据交互作为对象的主要类。DbContext负责以下活动:
(1)EntitySet: DbContext包含映射到数据库表的所有实体的实体集(DbSet <TEntity>)。
(2)查询(Querying): DbContext 将 LINQ-to-Entities 查询转换为SQL查询并将其发送到数据库。
(3)更改跟踪(Change Tracking):跟踪实体在从数据库查询后发生的更改。
(4)持久数据(Persisting Data):它还根据实体的状态对数据库执行插入,更新和删除操作。
(5)缓存(Caching): DbContext默认进行一级缓存。它存储在上下文类生命周期中已经被检索的实体。
(6)管理关系(Manage Relationship): DbContext还使用DB-First或Model-First方法使用CSDL,MSL和SSDL或者使用Code-First方法使用流利的API来管理关系。
(7)对象实现(Object Materialization): DbContext将原始表数据转换为实体对象。

参考2:https://blog.csdn.net/lnazj/article/details/79066192

DbContext类的方法:

  • Entry: 获取DbEntityEntry给定的实体。该条目提供访问更改实体的跟踪信息和操作。
  • SavaChanges: 对已添加,已修改或已删除状态的实体的数据库执行INSERT,UPDATE或DELETE命令。
  • SaveChangesAsync: SaveChanges()的异步方法
  • Set: 创建一个DbSet可以用来查询和保存实例的TEntity。
  • OnModelCreating 重写此方法以进一步配置通过DbSet派生上下文中属性中公开的实体类型按惯例发现的模型。

**DbSet:**表示可用于创建,读取,更新和删除操作的实体集。

DbSet 常用的方法:

Add:将给定的实体添加到添加状态的上下文中。当保存更改时,添加状态中的实体将被插入到数据库中。保存更改后,对象状态将更改为“未更改”。

Remove:将给定的实体标记为已删除。保存更改后,实体将从数据库中删除。在调用此方法之前,实体必须存在于其他某个状态的上下文中。

以下是生成的excellentmcoinEntities类(派生DbContext的上下文类)的示例:

//------------------------------------------------------------------------------
// <auto-generated>
//    此代码是根据模板生成的。
//
//    手动更改此文件可能会导致应用程序中发生异常行为。
//    如果重新生成代码,则将覆盖对此文件的手动更改。
// </auto-generated>
//------------------------------------------------------------------------------
 
namespace Model
{
    using System;
    using System.Data.Entity;
    using System.Data.Entity.Infrastructure;
    
    public partial class excellentmcoinEntities : DbContext
    {
        public excellentmcoinEntities()
            : base("name=excellentmcoinEntities")
        {
        }
    
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            throw new UnintentionalCodeFirstException();
        }
    //DbSet表示上下文中指定类型的所有实体的集合或可从数据库中查询的指定类型的所有实体的集合
        //巴拉巴拉 原博文这里写了很多个,删了些
        public DbSet<t_stageconfluence> t_stageconfluence { get; set; }
        public DbSet<t_states> t_states { get; set; }
        public DbSet<t_suggesstion> t_suggesstion { get; set; }
        public DbSet<t_type> t_type { get; set; }
        public DbSet<t_user> t_user { get; set; }
        public DbSet<t_attributerecord> t_attributerecord { get; set; }
        public DbSet<t_notice> t_notice { get; set; }
    }
}

使用:

//增
excellentmcoinEntities db = new excellentmcoinEntities();
//创建对象实体,注意,这里需要对所有属性进行赋值(除了自动增长主键外),如果不赋值,则会数据库中会被设置为NULL(注意是否可空)
var user = new User
            {
                Name = "bomo",
                Age = 21,
                Gender = "male"
            };
db.User.Add(user);
db.SaveChanges();

//删
 public bool deleteUser(string UserID)
        {
         excellentmcoinEntities dbcontext = new excellentmcoinEntities();
            try
            {
                string[] testUserID = UserID.Split(',');
                
                for (int i = 0; i < testUserID.Length; i++)
                {
                    t_user usermodel = dbcontext.t_user.Find(testUserID[i]);
                    dbcontext.t_user.Remove(usermodel);
                    int flag = dbcontext.SaveChanges();
                    if (flag > 0)
                    {
                        continue;
                    }
                    else
                    {
                        return false;
                    }
                }
                return true;
            }
            catch (Exception)
            {
 
                throw new Exception("删除失败");
            }
        }

//改
public bool EditUser(t_user usermodel)
        {
            excellentmcoinEntities dbcontext = new excellentmcoinEntities();
            try
            {
                t_user user = dbcontext.t_user.Find(usermodel.userID);
                if (user == null)
                {
                    return false;
                }
                user.userName = usermodel.userName;
                user.levelID = usermodel.levelID;
                user.state = usermodel.state;
                user.passWord = usermodel.passWord;
 
                //修改多个字段值
                dbcontext.Entry<t_user>(user).State = System.Data.EntityState.Modified;
                dbcontext.SaveChanges();
                return true;
            }
            catch (Exception)
            {
 
                throw new Exception("用户修改失败");
            }
 
        }

//查
   public List<userModel> QueryAUser()
        {
            //定义了上下文实体
            excellentmcoinEntities dbcontext = new excellentmcoinEntities();
            var allUser = (from u in dbcontext.t_user
                           join g in dbcontext.t_grade on u.gradeID equals g.gradeID
                           orderby u.totalMcoin descending
                           select new userModel()
                           {
                               userID = u.userID,
                               userName = u.userName,
                               userGrade = g.userGrade,
                               totalMcoin = u.totalMcoin,
                               gradeID = g.gradeID,
                           }).ToList();
            return allUser;
        }

DbContext通常与包含模型的根实体的DbSet 属性的派生类型一起使用。当派生类的实例被创建时,这些集合会自动初始化。

虚拟DbSet<>

https://cloud.tencent.com/developer/ask/sof/48027

首先在实体框架代码中,当我声明实体时,我必须使用DbSet<>类型的属性。例如:

public DbSet<Product> Products { get; set; }
public DbSet<Customer> Customers { get; set; }

最近,我遇到了被声明为虚拟的DbSet<>。

public virtual DbSet<Product> Products { get; set; }
public virtual DbSet<Customer> Customers { get; set; }

有什么关系?启用了哪些EF功能?

EF数据库持久化

数据持久化就是将内存中的数据模型转换为存储模型,以及将存储模型转换为内存中的数据模型的统称. 数据模型可以是任何数据结构或对象模型,存储模型可以是关系模型、XML、二进制流等。(即 内存 ----》磁盘)

使用实体框架将实体持久(保存)到数据库有两种情况:

1、连接方案(在连接的场景下保存数据)

在连接场景中,同一个上下文类(从DbContext派生)实例用于检索和保存实体,并且跟踪所有的实体。

下图是连接场景下CUD操作

这里写图片描述

根据上图,实体框架为调用方法EntityState时添加,修改或删除的实体构建并执行INSERT,UPDATE或DELETE语句 DbContext.SaveChanges()。在连接场景中,一个实例DbContext跟踪所有实体,所以EntityState无论何时创建,修改或删除实体,它都会自动设置每个实体的适当值。

插入数据
使用DbSet.Add方法将新实体添加到上下文(实例DbContext),当您调用该SaveChanges()方法时,该实例将在数据库中插入新记录。

usingvar context = new SchoolDBEntities())
{
    var std = new  Student()
    {
        FirstName = “Bill”,
        LastName = “门”
    };
    context.Students.Add(std);

    context.SaveChanges();
}

在上面的例子中,使用EntityState context.Students.Add(std)将一个新创建的Student实体实例添加到上下文中Added。

该context.SaveChanges()方法构建并执行以下INSERT语句到数据库。(看到了使用了参数化查询)

exec sp_executesql N'INSERT [dbo].[Students]([FirstName], [LastName])
VALUES (@0, @1)
SELECT [StudentId]
FROM [dbo].[Students]
WHERE @@ROWCOUNT > 0 AND [StudentId] = scope_identity()',N
''@0 nvarchar(max) ,@1 nvarchar(max) ',@0=N'Bill',@1=N'Gates'
go

更新数据
在连接场景中,EF API跟踪使用上下文检索的所有实体。因此,当您编辑实体数据时,EF将自动标记EntityState为Modified,当您调用该SaveChanges()方法时,它会在数据库中建立并执行下面的Update语句,会导致数据库中更新的语句。

using (var context = new SchoolDBEntities())
{
    var std = context.Students.First<Student>(); 
    std.FirstName = "Steve";
    context.SaveChanges();
}

在上面的例子中,我们使用数据库检索第一个学生context.Students.First()。一旦我们修改了FirstName,上下文将它设置EntityState为Modified因为在DbContext实例(上下文)范围内执行的修改。

所以,当我们调用这个SaveChanges()方法时,它会在数据库中建立并执行下面的Update语句。

exec sp_executesql N'UPDATE [dbo].[Students]
SET [FirstName] = @0
WHERE ([StudentId] = @1)',
N'@0 nvarchar(max) ,@1 int',@0=N'Steve',@1=2
Go

删除数据

using (var context = new SchoolDBEntities())
{
    var std = context.Students.First<Student>();
    context.Students.Remove(std);

context.SaveChanges();

}

context.Students.Remove(std)将std实体对象标记为Deleted。因此,当执行context.SaveChanges()时,EF将在数据库中构建并执行以下DELETE语句。

exec sp_executesql N'DELETE [dbo].[Students]
WHERE ([StudentId] = @0)',N'@0 int',@0=1
Go

总结:
通过让DbSet(实体集) 来执行CUD (添加,更新,删除) 操作实体,然后会更改EntityState 属性为(Added,Modified,Deleted),然后当上下文context 执行SaveChanges方法时,会根据EntityState 属性值来操纵数据库,执行insert,update或者delete 操作。OnsaveChange方法封装了对数据库的增删改查操作

2、断开方案

webclient

WebClient读取网络数据: https://blog.csdn.net/weixin_30512043/article/details/97790366

WebClient.DownloadData (String) 以 Byte 数组形式通过指定的 URI 下载资源

WebClient.DownloadData (Uri) 以 Byte 数组形式通过指定的 URI 下载资源

WebClient.DownloadFile (String, String) 将具有指定 URI 的资源下载到本地文件

WebClient.DownloadFile (Uri, String) 将具有指定 URI 的资源下载到本地文件

WebClient.DownloadString (String) 以 String 形式下载指定的资源

WebClient.DownloadString (Uri) 以 Uri 形式下载指定的资源

以上这些方法,均有xxxAsync方法与之对应,目的是使得xxx方法不会阻止调用线程。如WebClient.DownloadFileAsync (Uri, String)方法,就是异步下。

其中,WebClient.DownloadData (Uri) 方法返回Byte数组,容易出现中文乱码问题,因为中文是双字节数据,而返回byte类型,因此这时候,中文便容易出现乱码,解决方案可能是转换为unicode什么的吧。

建议用 WebClient.DownloadString(Uri) ,如果此时还出现乱码问题,便是字符集问题了,可以用

WebClient client = new WebClient();
string s = client.DownloadString("http://www.cnblogs.com");
s=Encoding.UTF8.GetString(Encoding.Default.GetBytes(s);

跨域配置

https://blog.csdn.net/qq_39569480/article/details/121770414

https://blog.csdn.net/vincent_ling/article/details/51714691

跨域配置也是在 startup.cs 文件中的ConfigureServices方法下

services.AddCors(options =>
            {
                // Policy 名称 CorsPolicy 是自定义的,可以自己改
                options.AddPolicy("qwer", policy =>
                {
                    // 设定允许跨域的来源,有多个的话可以用 , 隔开
                    string CorsUrl= Configuration.GetConnectionString("CorsOrigins");//通过注入的IConfiguration 获取appsetting.json中的自定义路径
                    string[] CoreArray = CorsUrl.Split(',');//appsetting.json中的配置
                    //policy.WithOrigins("http://localhost:8080", "http://192.168.0.86:8080","http://123.123.123.123:5555")//写死的方式,不方便
                    policy.WithOrigins(CoreArray)
                            .AllowAnyHeader()
                            .AllowAnyMethod()
                            .AllowCredentials();//这次代码审计的源码里,这个没开,就没审计这个漏洞
                });
            });

同时修改 startup.cs 文件中的Configure 方法

添加

app.UseCors("qwer");//必须位于UserMvc之前 

搭建跨域请求环境的详解

思路:

  1. 使用express搭建第一个服务A(http://localhost:8000),运行在8000端口上;
  2. A服务托管index.html(用于在前端页面发送网络请求)文件;
  3. A服务中写一个处理请求的路由,加载index.html页面时,种下cookie(这里种cookie为了在请求B服务时携带上);
  4. 使用express搭建第二个服务B(http://localhost:8003),运行在8003端口上;
  5. A服务托管的index.html页面去请求B服务,然后把cookie传过去;

总结:

  1. A前端请求时在request对象中配置"withCredentials": true

XMLHttpRequest.withCredentials属性是一个Boolean类型,它指示了是否该使用类似 cookies,authorization headers(头部授权)或者 TLS 客户端证书这一类资格证书来创建一个跨站点访问控制(cross-site Access-Control)请求。

  1. B服务端在responseheader中配置"Access-Control-Allow-Origin", "http://localhost:8000";
  2. B服务端在responseheader中配置"Access-Control-Allow-Credentials", "true"

sql注入防护

format:https://blog.csdn.net/jirigala/article/details/5829455

format并不具备防护sql注入的功能,仅仅只是拼接字符串

 string  userName  =   " lili" ;
 string  password  =   " 123456 " ;
 string  sqlQuery  =   string .Format( " SELECT * FROM Base_User WHERE UserName='{0}' AND UserPassword='{1}' " , userName, password);

https://www.jb51.net/article/45881.htm

https://blog.csdn.net/Ca_va/article/details/107973851

防护方法:

1、使用SQL参数化 @

string sqlStr="select * from [Users] where UserName=@UserName and Password=@Password"
  //然后创建命令对象的代码时,修改为:
SqlCommand cmd = new SqlCommand(sqlStr,conn);
cmd.Parameters.AddWithValue("@UserName",txtUserName.Text.Trim());
cmd.Parameters.AddWithValue("@Password",txtUserPassword.Text.Trim());

JWT

安全经典JWT算法漏洞:https://blog.csdn.net/kali_Ma/article/details/121609999

Asp.NetCore3.1 WebApi 使用Jwt 授权认证使用:https://blog.csdn.net/qq_18932003/article/details/119985995

1:导入NuGet包 Microsoft.AspNetCore.Authentication.JwtBearer

2 : 配置 jwt相关信息

3:在 startUp中

public void ConfigureServices(IServiceCollection services){
#region JWT 认证
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options => {
var jsonmodel = AppJsonHelper.InitJsonModel();
options.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = jsonmodel.Issuer,// Configuration["JwtSetting:Issuer"],
ValidAudience = jsonmodel.Audience,// Configuration["JwtSetting:Audience"],
// IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JwtSetting:SecurityKey"])),
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jsonmodel.TockenSecrete)),
// 默认允许 300s 的时间偏移量,设置为0即可
ClockSkew = TimeSpan.Zero
};
});
#endregion
}
 
//注意需要放在addmvc上面 services.AddMvc();
 
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseAuthentication();//身份验证
app.UseAuthorization();// 授权
}

4:使用时在Controller /action 上打上特性 [Authorize]

可以单独在Action上打上特性[Authorize] 不需要检查授权认证的话打上特性: [AllowAnonymous]

两个特性类都在如下命名空间下:using Microsoft.AspNetCore.Authorization;

5:登陆成功后端并返回生成的Token,可以在PostMan上面测试,和JWT.io官网上面来测试

6: 发送请求到后端,带上Token 如Get 😕/localhost:5000/user/login
Key value
Authorization Bearer qweTdfdsfsJhdsfd0.fdsfdsgfdsewDDQDD.fdsfdsg

7:action上面的code

[HttpPost, Route("Login")]
        public ApiResult Login(personnel p)
        {
            ApiResult result = new ApiResult();
            try
            {
                string tockenStr = ZrfJwtHelper.GetTocken(p);
                result.data = tockenStr;
                result.code = statuCode.success;
                result.message = "获取成功!";
            }
            catch (Exception ex)
            {
                result.message = "查询异常:" + ex.Message;
            }
            return result;
        }
 
 
        [HttpPost, Route("authTest")]
        [Authorize]
        [AllowAnonymous]// 跳过授权认证
        public ApiResult authTest(string accesTocken)
        {
            ApiResult result = new ApiResult();
            try
            {
                var info = ZrfJwtHelper.GetTockenInfo(accesTocken);
                result.data = info;
                result.code = statuCode.success;
                result.message = "获取成功!";
            }
            catch (Exception ex)
            {
                result.message = "查询异常:" + ex.Message;
            }
            return result;
        }

8:完整的Jwt代码封装

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ZRFCoreTestMongoDB.Commoms
{
    using Microsoft.AspNetCore.Http;
    using Microsoft.IdentityModel.Tokens;
    using System.IdentityModel.Tokens.Jwt;
    using System.Security.Claims;
    using System.Text;
    using ZRFCoreTestMongoDB.Model;
 
    public class ZrfJwtHelper
    {
        /// <summary>
        /// 生成Tocken
        /// </summary>
        /// <param name="p"></param>
        /// <returns></returns>
        public static string GetTocken(personnel p)
        {
            //读取配置文件获得Jwt的json文件信息
            var model = AppJsonHelper.InitJsonModel();
            string _issuer = model.Issuer;//分发者
            string audience = model.Audience;//接受者
            string TockenSecrete = model.TockenSecrete;//秘钥
 
            //秘钥
            var securityKey = new SigningCredentials(new SymmetricSecurityKey(Encoding.ASCII.GetBytes(TockenSecrete)), SecurityAlgorithms.HmacSha256);
            // 設定要加入到 JWT Token 中的聲明資訊(Claims)
            //var claims = new List<Claim>();
             //在 RFC 7519 規格中(Section#4),總共定義了 7 個預設的 Claims,我們應該只用的到兩種!
            claims.Add(new Claim(JwtRegisteredClaimNames.Iss, issuer));
            //claims.Add(new Claim(JwtRegisteredClaimNames.Sub, userInfo.UserId));
 
            //Claim
            var claims = new Claim[] {
                    new Claim(JwtRegisteredClaimNames.Sid,p.Uid),
                    new Claim(JwtRegisteredClaimNames.Iss,_issuer),
                    new Claim(JwtRegisteredClaimNames.Sub,p.Name),
                    new Claim("Guid",Guid.NewGuid().ToString("D")),
                    new Claim("Roleid",p.Roleid.ToString()),
                    new Claim("Age",p.Age.ToString()),
                    new Claim("BirthDay",p.BirthDay.ToString())
            };
 
            SecurityToken securityToken = new JwtSecurityToken(
                issuer: _issuer,
                audience: audience,
                signingCredentials: securityKey,
                expires: DateTime.Now.AddMinutes(2),//过期时间
                claims: claims
                );
 
            return new JwtSecurityTokenHandler().WriteToken(securityToken);
        }
 
        /// <summary>
        /// 获取accessTocken
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public static string GetTockenString(HttpContext context)
        {
            return context != null ? context.Request.Headers["Authorization"].ToString() : "";
        }
 
        /// <summary>
        /// 解析Jwt生成的 Tocken
        /// </summary>
        /// <param name="accesTocken"></param>
        /// <returns></returns>
        public static TockenInfo GetTockenInfo(string accesTocken)
        {
            try
            {
                if (accesTocken.Contains("Bearer")) //防止前端传过来的tocken 为待了 Bearer 的字符串
                {
                    accesTocken = accesTocken.Replace("Bearer ", "");
                }
                var tockHandler = new JwtSecurityToken(accesTocken);
                TockenInfo info = new TockenInfo
                {
                    // Age=tockHandler.Claims.FirstOrDefault(c=>c.Type==JwtRegisteredClaimNames.Email)
                    Uid = tockHandler.Claims.FirstOrDefault(c => c.Type == JwtRegisteredClaimNames.Sid).Value,
                    Name = tockHandler.Claims.FirstOrDefault(c => c.Type ==JwtRegisteredClaimNames.Sub).Value,//在于自己来定义了,上面生成是和下面获取时Key要一致
 
                    Age = tockHandler.Claims.FirstOrDefault(c => c.Type == "Age").Value,
                    BirthDay = tockHandler.Claims.FirstOrDefault(c => c.Type == "BirthDay").Value,
                    Roleid = tockHandler.Claims.FirstOrDefault(c => c.Type == "Roleid").Value,
                };
                return info;
            }
            catch (Exception ex)
            {
                throw new Exception("解析Tocken时错误!");
            }
        }
    }
    public class TockenInfo
    {
        public string Uid { get; set; }
        public string Name { get; set; }
        public string Age { get; set; }
        public string BirthDay { get; set; }
        public string Roleid { get; set; }
    }
}

9:模型实体

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
 
namespace ZRFCoreTestMongoDB.Model
{
    using System.ComponentModel.DataAnnotations;
    [Serializable]
    public class personnel
    {
 
        [Required(ErrorMessage = "姓名必填")]
        [StringLength(maximumLength: 10, ErrorMessage = "姓名最多是10个字符")]
        [MinLength(2, ErrorMessage = "姓名长度最少为两个字符")]
        public string Name { get; set; }
 
        [Range(1, 150, ErrorMessage = "年龄范围为:1-150")]
        public int Age { get; set; }
        [DataType(DataType.Date, ErrorMessage = "生日不学为日期格式,例如:1998-10-10")]
        public DateTime BirthDay { get; set; }
 
        [Required(ErrorMessage = "密码必填")]
        [StringLength(maximumLength: 10, MinimumLength = 6, ErrorMessage = "密码长度最多10位")]
        public string Password { get; set; }
        public int Roleid { get; set; }
        public string Uid { get; set; }
    }
}

如何使用 ASP.NET Core 从 DbContext 中的 JWT 获取用户名?:https://qa.1r1g.com/sf/ask/3558601721/

_httpContext.HttpContext.User.Claims.SingleOrDefault(
        c => c.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier")?.Value
  //该url等同于使用 ClaimTypes.NameIdentifier
  //详见 ClaimTypes 类:https://docs.microsoft.com/zh-cn/dotnet/api/system.security.claims.claimtypes?view=netframework-4.8

C# 中List集合类的Single,SingleOrDefault,First,FirstOrDefault的区别 :

Single(条件):确定只符合条件的结果只有一个值;否则报错,如果有多个值则报Sequence contains more than one matching element 如果没有符合的则报Sequence contains no matching element。

SingleOrDefault(条件):在确定的条件下,只有一个或者0个值;如果一个以上的值符合条件 则报错。

First(条件):在确定条件下,至少有一个值;否则报Sequence contains no matching element错误。

FirstOrDefault(条件):在确定条件下,可以有0个包括0个以上的值。

int[] nums = { 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024 };             
Console.WriteLine("single:" + nums.Single(x => x>4 && x<9));            
Console.WriteLine("SingleOrDefault:" + nums.SingleOrDefault(x => x > 4 && x < 7));           Console.WriteLine("First:" + nums.First(x => x > 4 && x < 512));            
Console.WriteLine("FirstOrDefault:" + nums.FirstOrDefault(x => x > 513 && x < 512));
bearer令牌

BEARER类型的token是在RFC6750中定义的一种 token 类型。BEARER 类型 token是建立在 HTTP/1.1 版本之上的token类型,需要 TLS (Transport Layer Security) 提供安全支持,该协议主要规定了BEARER 类型 token的客户端请求和服务端验证的具体细节。

由 Authorization Server 在 Resource Owner 的允许下核发给 Client ,Resource Server 只要认在这个 Token 就可以认定 Client 已经获取 Resource Owner 的许可,不需要用密码学的方式来验证这个 Token 的真伪。

Bearer Token 的格式:

Bearer XXXXXXXX

其中 XXXXXXXX 的格式 b64token ,ABNF 的定义:

b64token = 1*( ALPHA / DIGIT / "-" / "." / "_" / "~" / "+" / "/" ) *"="

写成 Regular Expression 即是:

/[A-Za-z0-9\-\._~\+\/]+=*/

常见的OAuth令牌漏洞:https://blog.csdn.net/github_38730134/article/details/114061797

使用beare令牌的风险及注意事项:

如果攻击者能截获访问令牌,他就能访问该令牌的权限范围内的所有资源,使用bearer令牌客户端不需要证明其拥有其他额外的安全信息,比如加密密钥。以下这些与OAuth bearer令牌相关的风险与其他基于令牌的协议是共通的:

  • 令牌伪造。攻击者可能会构造加令牌或者篡改已有的有效令牌,导致资源服务器授与客户度不当的访问权限。
  • 令牌重放。攻击者会尝试使用过去使用过并且已经过期的旧令牌。在这种情况下服务器不因该返回任何有效信息,而应该提示错误
  • 令牌重定向。攻击者将用于某一资源服务器的令牌用来访问另一资源服务器,而该资源服务器误认为令牌有效。攻击者先合法地获取某一特定资源服务器的访问令牌,然后将该访问令牌出示给另一资源服务器
  • 令牌信息泄露。令牌可能会含有一些关于系统的敏感信息,而这些信息是不应该透露给攻击者的

bearer token

授权

https://blog.51cto.com/axzxs/1962246

在《asp.net core认证与授权》中讲解了固定和自定义角色授权系统权限,其实我们还可以通过其他方式来授权
,比如可以通过角色组,用户名,生日等,但这些主要取决于ClaimTypes,其实我们也可以自定义键值来授权,这
些统一叫策略授权,其中更强大的是,我们可以自定义授权Handler来达到灵活授权,下面一一展开。

首先看基于角色组,或用户名,或基于ClaimType或自定义键值等授权策略。

这些都是通过Services.AddAuthotization添加,并且是AuthorizationOptions来AddPolicy,这里策略的名称统一用RequireClaim来命名,不同的请求的策略名称各不相同,如用户名时就用 policy.RequireUserName(),

同时,在登录时,验证成功后,要添加相应的Claim到ClaimsIdentity中:
Startup.cs

比较详细,懒得写了,看原博吧

ps:虽然搜了那么多文章,看完自己没运行实现,就还不咋理解其实,不过总算不晕头转向了

也类似的一篇文章:https://wenku.baidu.com/view/65d12ce388d63186bceb19e8b8f67c1cfad6ee6b.html


算了,重新开一篇,从头介绍,感觉这篇写的比较清晰,我容易理解些

https://blog.csdn.net/hiliqi/article/details/80721240

在asp.net core中,微软提供了基于认证(Authentication)和授权(Authorization)的方式,来实现权限管理的。

本篇博文,介绍基于固定角色的权限管理和自定义角色权限管理,本文内容,更适合传统行业的BS应用,而非互联网应用。

在asp.net core中,认证(Authentication)通常是在Login的Post Action中进行用户名或密码来验证用户是否正确,如果通过验证,即该用户就会获得一个或几个特定的角色,通过ClaimTypes.Role来存储角色,从而当一个请求到达时,用这个角色和Controller或Action上加的特性 [Authorize(Roles = “admin,system”)]来授权是否有权访问该Action。

本文中的自定义角色,会把验证放在中间件中进行处理。

一、固定角色

把角色与具体的Controller或Action直接关联起来,整个系统中的角色是固定的,每种角色可以访问那些Controller或Action也是固定的,这做法比较适合小型项目,角色分工非常明确的项目

ps:这个文章的举例和我的案例不一样,我的案例是jwt,bearer的,这个是cookie

1、startup.cs

需要在ConfigureServices中注入Cookie的相关信息,options是CookieAuthenticationOptions,关于这个类型提供如下属性,可参考:https://docs.microsoft.com/en-us/aspnet/core/security/authentication/cookie?tabs=aspnetcore2x

public void ConfigureServices(IServiceCollection services)
 2         {
 3             services.AddMvc();
 4             //添加认证Cookie信息
 5             services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
 6              .AddCookie(options =>
 7              {
 8                  options.LoginPath = new PathString("/login");
 9                  options.AccessDeniedPath = new PathString("/denied");
10              });
11         }
12 
13         public void Configure(IApplicationBuilder app, IHostingEnvironment env)
14         {
15             if (env.IsDevelopment())
16             {
17                 app.UseDeveloperExceptionPage();
18                 app.UseBrowserLink();
19             }
20             else
21             {
22                 app.UseExceptionHandler("/Home/Error");
23             }
24             app.UseStaticFiles();
25             //验证中间件(自定义角色时使用)
26             app.UseAuthentication();
27             app.UseMvc(routes =>
28             {
29                 routes.MapRoute(
30                     name: "default",
31                     template: "{controller=Home}/{action=Index}/{id?}");
32             });
33         }

2、HomeController.cs 它提供了登录的一些信息,或登录生成Cookie的一些信息

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Diagnostics;
 4 using System.Linq;
 5 using System.Threading.Tasks;
 6 using Microsoft.AspNetCore.Mvc;
 7 using RolePrivilegeManagement.Models;
 8 using System.Security.Claims;
 9 using Microsoft.AspNetCore.Authentication;
10 using Microsoft.AspNetCore.Authentication.Cookies;
11 using Microsoft.AspNetCore.Authorization;
12 
13 namespace RolePrivilegeManagement.Controllers
14 {
  //HomeController上的 [Authorize(Roles=”admin,system”)] 角色和权限的关系时,所有Action只有admin和system两个角色能访问到
15     [Authorize(Roles = "admin,system")]
16     public class HomeController : Controller
17     {
18         public IActionResult Index()
19         {
20             return View();
21         }
  //About上的 [Authorize(Roles=”admin”)] 声明这个action只能admin角色访问
22         [Authorize(Roles = "admin")]
23         public IActionResult About()
24         {
25             ViewData["Message"] = "Your application description page.";
26             return View();
27         }
  //Contact上的 [Authorize(Roles=”system”)] 声明这个action只能system角色访问
28         [Authorize(Roles = "system")]
29         public IActionResult Contact()
30         {
31             ViewData["Message"] = "Your contact page.";
32             return View();
33         }
34         public IActionResult Error()
35         {
36             return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
37         }
  //如果action上声明的是[AllowAnomymous],说明不受授权管理,可以直接访问。
38         [AllowAnonymous]
39         [HttpGet("login")]
40         public IActionResult Login(string returnUrl = null)
41         {//对于Login Get的Action,把returnUrl用户想要访问的地址(有可能用户记录下想要访问的url了,但系统会转到登录页,登录成功后直接跳转到想要访问的returnUrl页)
42             TempData["returnUrl"] = returnUrl;
43             return View();
44         }
45         [AllowAnonymous]
46         [HttpPost("login")]
47         public async Task<IActionResult> Login(string userName, string password, string returnUrl = null)
48         {
49             var list = new List<dynamic> {
50                 new { UserName = "gsw", Password = "111111", Role = "admin" },
51                 new { UserName = "aaa", Password = "222222", Role = "system" }
52             };
53             var user = list.SingleOrDefault(s => s.UserName == userName && s.Password == password);
54             if (user!=null)
55             {
56                 //用户标识
57                 var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);
    //对于Login Post的Action,验证用户和密码,成功后,定义一个ClaimsIdentity,把用户名和角色,和用户姓名的声明都添回进来(这个角色,就是用来验证可访问action的角色 )作来该用户标识,
58                 identity.AddClaim(new Claim(ClaimTypes.Sid, userName));
59                 identity.AddClaim(new Claim(ClaimTypes.Name, user.Name));
60                 identity.AddClaim(new Claim(ClaimTypes.Role, user.Role));
  //接下来调用 HttpContext.SignInAsync 进行登录,注意此方法的第一个参数,必需与StartUp.cs中services.AddAuthentication的参数相同,AddAuthentication是设置登录,SigninAsync是按设置参数进行登录
61                 await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(identity));
62                 if (returnUrl == null)
63                 {
64                     returnUrl = TempData["returnUrl"]?.ToString();
65                 }
66                 if (returnUrl != null)
67                 {
68                     return Redirect(returnUrl);
69                 }
70                 else
71                 {
72                     return RedirectToAction(nameof(HomeController.Index), "Home");
73                 }
74             }
75             else
76             {
77                 const string badUserNameOrPasswordMessage = "用户名或密码错误!";
78                 return BadRequest(badUserNameOrPasswordMessage);
79             }
80         }
81         [HttpGet("logout")]
82         public async Task<IActionResult> Logout()
83         {
84             await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
85             return RedirectToAction("Index", "Home");
86         }
87         [AllowAnonymous]
88         [HttpGet("denied")]
89         public IActionResult Denied()
90         {
91             return View();
92         }
93     }
94 }
//Ps:我的案例,也是登录成功后就获取jwt授权,把信息存储进去(type=‘bearer’),生成token(包含颁发者,签名证书等很多信息)

在登录成功后的任何页面都可以用 @User.Identity.Name 就可以获取用户姓名,同时用 @User.Claims.SingleOrDefault(s=>s.Type== System.Security.Claims.ClaimTypes.Sid).Value 可以获取用户名或角色。

3、前端_Layout.cshtml

 1    <nav class="navbar navbar-inverse navbar-fixed-top">
 2         <div class="container">
 3             <div class="navbar-header">
 4                 <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
 5                     <span class="sr-only">Toggle navigation</span>
 6                     <span class="icon-bar"></span>
 7                     <span class="icon-bar"></span>
 8                     <span class="icon-bar"></span>
 9                 </button>
10                 <a asp-area="" asp-controller="Home" asp-action="Index" class="navbar-brand">RolePrivilegeManagement</a>
11             </div>
12             <div class="navbar-collapse collapse">
13                 <ul class="nav navbar-nav">
14                     <li><a asp-area="" asp-controller="Home" asp-action="Index">Home</a></li>
15                     <li><a asp-area="" asp-controller="Home" asp-action="About">About</a></li>
16                     <li><a asp-area="" asp-controller="Home" asp-action="Contact">Contact</a></li>
17                 </ul>
18                 <ul class="" style="float:right; margin:0;">
19                     <li style="overflow:hidden;">
20                         <div style="float:left;line-height:50px;margin-right:10px;">
21                             <span style="color:#ffffff">当前用户:@User.Identity.Name</span>
22                         </div>
23                         <div style="float:left;line-height:50px;">
24                             <a asp-area="" asp-controller="Home" asp-action="Logout">注销</a>
25                         </div>
26                     </li>
27                 </ul>
28             </div>
29         </div>
30     </nav>

二、自定义角色

1、startup.cs

自定义角色与固定角色不同之处在于多了一个中间件(关于中间件学习参看:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware),即在Configure方法中,一定要在 app.UseAuthentication 下面添加验证权限的中间件,因为 UseAuthentication要从Cookie中加载通过验证的用户信息到Context.User中,所以一定放在加载完后才能去验用户信息(当然自己读取Cookie也可以)

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Threading.Tasks;
 5 using Microsoft.AspNetCore.Builder;
 6 using Microsoft.AspNetCore.Hosting;
 7 using Microsoft.Extensions.Configuration;
 8 using Microsoft.Extensions.DependencyInjection;
 9 using Microsoft.AspNetCore.Authentication.Cookies;
10 using Microsoft.AspNetCore.Http;
11 using PrivilegeManagement.Middleware;
12 
13 namespace PrivilegeManagement
14 {
15     public class Startup
16     {
17         public Startup(IConfiguration configuration)
18         {
19             Configuration = configuration;
20         }
21         public IConfiguration Configuration { get; }
22 
23         public void ConfigureServices(IServiceCollection services)
24         {
25             services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
26            .AddCookie(options =>
27            {
28                options.LoginPath = new PathString("/login");
29                options.AccessDeniedPath = new PathString("/denied");
30            }
31            );
32             services.AddMvc();
33         }
34 
35         public void Configure(IApplicationBuilder app, IHostingEnvironment env)
36         {
37             if (env.IsDevelopment())
38             {
39                 app.UseDeveloperExceptionPage();
40                 app.UseBrowserLink();
41             }
42             else
43             {
44                 app.UseExceptionHandler("/Home/Error");
45             }
46 
47             app.UseStaticFiles();
48             //验证中间件
49             app.UseAuthentication();
50             添加权限中间件, 一定要放在app.UseAuthentication后
51             app.UsePermission(new PermissionMiddlewareOption()
52             {
53                 LoginAction = @"/login",
54                 NoPermissionAction = @"/denied",
55                 //这个集合从数据库中查出所有用户的全部权限
56                 UserPerssions = new List<UserPermission>()
57                  {
58                      new UserPermission { Url="/", UserName="gsw"},
59                      new UserPermission { Url="/home/contact", UserName="gsw"},
60                      new UserPermission { Url="/home/about", UserName="aaa"},
61                      new UserPermission { Url="/", UserName="aaa"}
62                  }
63             });
64             app.UseMvc(routes =>
65             {
66                 routes.MapRoute(
67                     name: "default",
68                     template: "{controller=Home}/{action=Index}/{id?}");
69             });
70         }
71     }
72 }
//ps:我的案例不涉及自定义角色

2、中间件PermissionMiddleware.cs

在Invoke中用了context.User,如上面所述,首先要调用app.UseAuthentication加载用户信息后才能在这里使用,这个中间件逻辑较简单,如果没有验证的一律放过去,不作处理,如果验证过(登录成功了),就要查看本次请求的url和这个用户可以访问的权限是否匹配,如不匹配,就跳转到拒绝页面(这个是在Startup.cs中添加中间件时,用NoPermissionAction = @"/denied"设置的)

 1 using Microsoft.AspNetCore.Http;
 2 using System;
 3 using System.Collections.Generic;
 4 using System.IO;
 5 using System.Linq;
 6 using System.Reflection;
 7 using System.Security.Claims;
 8 using System.Threading.Tasks;
 9 
10 namespace PrivilegeManagement.Middleware
11 {
12     /// <summary>
13     /// 权限中间件
14     /// </summary>
15     public class PermissionMiddleware
16     {
17         /// <summary>
18         /// 管道代理对象
19         /// </summary>
20         private readonly RequestDelegate _next;
21         /// <summary>
22         /// 权限中间件的配置选项
23         /// </summary>
24         private readonly PermissionMiddlewareOption _option;
25 
26         /// <summary>
27         /// 用户权限集合
28         /// </summary>
29         internal static List<UserPermission> _userPermissions;
30 
31         /// <summary>
32         /// 权限中间件构造
33         /// </summary>
34         /// <param name="next">管道代理对象</param>
35         /// <param name="permissionResitory">权限仓储对象</param>
36         /// <param name="option">权限中间件配置选项</param>
37         public PermissionMiddleware(RequestDelegate next, PermissionMiddlewareOption option)
38         {
39             _option = option;
40             _next = next;
41             _userPermissions = option.UserPerssions;
42         }       
43         /// <summary>
44         /// 调用管道
45         /// </summary>
46         /// <param name="context">请求上下文</param>
47         /// <returns></returns>
48         public Task Invoke(HttpContext context)
49         {
50             //请求Url
51             var questUrl = context.Request.Path.Value.ToLower();
52        
53             //是否经过验证
54             var isAuthenticated = context.User.Identity.IsAuthenticated;
55             if (isAuthenticated)
56             {
57                 if (_userPermissions.GroupBy(g=>g.Url).Where(w => w.Key.ToLower() == questUrl).Count() > 0)
58                 {
59                     //用户名
60                     var userName = context.User.Claims.SingleOrDefault(s => s.Type == ClaimTypes.Sid).Value;
61                     if (_userPermissions.Where(w => w.UserName == userName&&w.Url.ToLower()==questUrl).Count() > 0)
62                     {
63                         return this._next(context);
64                     }
65                     else
66                     {
67                         //无权限跳转到拒绝页面
68                         context.Response.Redirect(_option.NoPermissionAction);
69                     }
70                 }
71             }
72             return this._next(context);
73         }
74     }
75 }
//ps:我的案例,只判断了是否经过验证,而且我的案例的context是AuthorizationHandlerContext。验证成功,就使用它的Succeed(IAuthorizationRequirement)方法。else 验证不通过就return Task.CompletedTask;。
//详见:https://docs.microsoft.com/zh-cn/dotnet/api/microsoft.aspnetcore.authorization.authorizationhandlercontext?view=aspnetcore-6.0
//task使用:https://www.cnblogs.com/yunfeifei/p/4106318.html

3、扩展中间件类PermissionMiddlewareExtensions.cs

 1 using Microsoft.AspNetCore.Builder;
 2 using System;
 3 using System.Collections.Generic;
 4 using System.Linq;
 5 using System.Threading.Tasks;
 6 
 7 namespace PrivilegeManagement.Middleware
 8 {
 9     /// <summary>
10     /// 扩展权限中间件
11     /// </summary>
12     public static class PermissionMiddlewareExtensions
13     {
14         /// <summary>
15         /// 引入权限中间件
16         /// </summary>
17         /// <param name="builder">扩展类型</param>
18         /// <param name="option">权限中间件配置选项</param>
19         /// <returns></returns>
20         public static IApplicationBuilder UsePermission(
21               this IApplicationBuilder builder, PermissionMiddlewareOption option)
22         {
23             return builder.UseMiddleware<PermissionMiddleware>(option);
24         }
25     }
26 }

4、中间件属性PermissionMiddlewareOption.cs

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Threading.Tasks;
 5 
 6 namespace PrivilegeManagement.Middleware
 7 {
 8     /// <summary>
 9     /// 权限中间件选项
10     /// </summary>
11     public class PermissionMiddlewareOption
12     {
13         /// <summary>
14         /// 登录action
15         /// </summary>
16         public string LoginAction
17         { get; set; }
18         /// <summary>
19         /// 无权限导航action
20         /// </summary>
21         public string NoPermissionAction
22         { get; set; }
23 
24         /// <summary>
25         /// 用户权限集合
26         /// </summary>
27         public List<UserPermission> UserPerssions
28         { get; set; } = new List<UserPermission>();
29     }
30 }

5、中间件实体类UserPermission.cs

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Threading.Tasks;
 5 
 6 namespace PrivilegeManagement.Middleware
 7 {
 8     /// <summary>
 9     /// 用户权限
10     /// </summary>
11     public class UserPermission
12     {
13         /// <summary>
14         /// 用户名
15         /// </summary>
16         public string UserName
17         { get; set; }
18         /// <summary>
19         /// 请求Url
20         /// </summary>
21         public string Url
22         { get; set; }
23     }
24 }

6、BaseController.cs

自定义角色,因为不需要授权时带上角色,所以可以定义一个基Controller类 BaseController.cs,其他的Controller都继承BaseController,这样所有的action都可以通过中间件来验证,当然像登录,无权限提示页面还是在Action上加 [AllowAnomymous]

1 using Microsoft.AspNetCore.Authorization;
2 using Microsoft.AspNetCore.Mvc;
3 namespace PrivilegeManagement.Controllers
4 {
5     [Authorize]
6     public class BaseController:Controller
7     {
8     }
9 }

7、HomeController.cs

与固定角色的HomeController.cs差异只在 Controller 和 Action 上的 Authorize特性。

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Diagnostics;
 4 using System.Linq;
 5 using System.Threading.Tasks;
 6 using Microsoft.AspNetCore.Mvc;
 7 using PrivilegeManagement.Models;
 8 using Microsoft.AspNetCore.Authorization;
 9 using System.Security.Claims;
10 using Microsoft.AspNetCore.Authentication.Cookies;
11 using Microsoft.AspNetCore.Authentication;
12 
13 namespace PrivilegeManagement.Controllers
14 {
15  
16     public class HomeController : BaseController
17     {
18         public IActionResult Index()
19         {
20             return View();
21         }
22 
23         public IActionResult About()
24         {
25             ViewData["Message"] = "Your application description page.";
26             
27             return View();
28         }
29 
30         public IActionResult Contact()
31         {
32             ViewData["Message"] = "Your contact page.";
33 
34             return View();
35         }
36 
37         public IActionResult Error()
38         {
39             return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
40         }
41         [AllowAnonymous]
42         [HttpGet("login")]
43         public IActionResult Login(string returnUrl = null)
44         {
45             TempData["returnUrl"] = returnUrl;
46             return View();
47         }
48         [AllowAnonymous]
49         [HttpPost("login")]
50         public async Task<IActionResult> Login(string userName,string password, string returnUrl = null)
51         {
52             var list = new List<dynamic> {
53                 new { UserName = "gsw", Password = "111111", Role = "admin",Name="桂素伟" },
54                 new { UserName = "aaa", Password = "222222", Role = "system",Name="测试A" }
55             };
56             var user = list.SingleOrDefault(s => s.UserName == userName && s.Password == password);
57             if (user != null)
58             {
59                 //用户标识
60                 var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);
61                 identity.AddClaim(new Claim(ClaimTypes.Sid, userName));
62                 identity.AddClaim(new Claim(ClaimTypes.Name, user.Name));
63                 identity.AddClaim(new Claim(ClaimTypes.Role, user.Role));
64 
65                 await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(identity));
66                 if (returnUrl == null)
67                 {
68                     returnUrl = TempData["returnUrl"]?.ToString();
69                 }
70                 if (returnUrl != null)
71                 {
72                     return Redirect(returnUrl);
73                 }
74                 else
75                 {
76                     return RedirectToAction(nameof(HomeController.Index), "Home");
77                 }
78             }
79             else
80             {
81                 const string badUserNameOrPasswordMessage = "用户名或密码错误!";
82                 return BadRequest(badUserNameOrPasswordMessage);
83             }
84         }
85         [HttpGet("logout")]
86         public async Task<IActionResult> Logout()
87         {
88             await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
89             return RedirectToAction("Index", "Home");
90         }
91         [HttpGet("denied")]
92         public IActionResult Denied()
93         {
94             return View();
95         }
96     } 
97 }

⬆️ 这篇写得不错,看完,我基本明白了这个逻辑,以及C#的逻辑。

依赖注入

顺便看到了这个博主的这个文章:https://blog.csdn.net/hiliqi/article/details/80611209

也顺便学习一下

using System.Linq;

授权,jwt这块经常引用这个LinQ命名空间,就搜了一下,C# 真的有太多陌生的东西了

LINQ(读音link)代表语言集成查询(Language Integrated Query),是.NET 框架的扩展,它允许我们用SQL查询数据库的方式来查询数据的集合,使用它,你可以从 数据库、程序对象的集合以及XML文档 中查询数据

Linq 体系结构如下,包括3层架构,他不仅可以操作sql数据,还可以操作内存数据,如Object ,Entity ,XML 等。linq查询中,始终会用到对象,不管用户是操作 xml ,还是object,entity等,都可以使用统一的查询方式,linq 把查询和设置等操作封装起来了,他自己判断是执行sql 还是xml。

这里写图片描述

1、示例:查询数组中小于8的数字并输出。

一般步骤:获取数据源、创建查询、执行查询。需要注意的是,尽管查询在语句中定义,但直到最后的foreach语句请求其结果的时候才会执行

using System;
using System.Collections.Generic;
using System.Linq;

namespace LINK查询
{
 class Program
 {
 static void Main(string[] args)
 {
  int[] number = { 2, 4, 6, 8, 10 }; //获取数据源
  IEnumerable<int> lowNum = from n in number //创建并存储查询,不会执行操作
     where n < 8
     select n;

  foreach(var val in lowNum) //执行查询
  {
  Console.Write("{0} ", val);
  }

  Console.ReadKey();
 }
 }
}

2、查询表达式结构

from,join,let,where,orderby,group,select(指定所选定的对象哪部分应该被选择),查询延续into(查询延续子句可以接受查询的一部分结构并赋予一个名字,从而可以在查询的另一部分中使用)

var someInt = from a in groupA
    from b in groupB
    into groupAandB
    from c in groupAandB
    select c;

3、方法语法 和 查询语法

在使用LINQ写查询时可以使用两种形式的语法:

a:方法语法(method syntax) : 使用标准的方法调用,这些方法是一组叫做标准查询运算符的方法

b:查询语法(query method) : 看上去和SQL语句很相似,使用查询表达式形式书写。

微软推荐使用查询语法,因为它更易读。在编译时,CLR会将查询语法转换为方法语法

int[] num = { 2, 4, 6, 8, 10 };

 var numQuery = from number in num //查询语法
    where number < 8
    select number;
//os:这里的lambda表达式是很好理解的,但是刚接触 c# 的时候,那每个表达式写老长了,心累累
 var numMethod = num.Where(x => x < 8); //方法语法

它们得到的结果是一样的。方法语法中where的参数使用了Lambda表达式

=>

参考文章1

C#里的=>有两种用法:

  • 用于Lambda表达式里,此时的=>被称为lambda operator

在C# 3到C# 5版本间,=>只有此种用法。此时的=>C++的lambda表达式里的->类似,举个例子:

// 声明一个函数指针的对象, 也就是委托, 其函数签名为string f(Person)
Func<Person, string> nameProjection = p => p.Name;

// 上面这句, 等同于:
Func<Person, string> nameProjection = delegate (Person p) { return p.Name; };

//注:这里的 Func<T, TResult>,最后面的代表函数的返回类型,前面的代表函数的参数,Func只是.NET提供的委托模板

在C# 6的版本里,=>开始用于expression-bodied members , C# 表达式主体成员允许我们在单个表达式中定义成员(属性或方法)定义。

public int MaxHealth1 => x ? y:z;

// ⬆️ 等同于
public int MaxHealth1
{
    get
    {
        return x ? y:z;
    }
}

https://blog.csdn.net/plovjet/article/details/87874980

Lambda 表达式”是一个匿名函数,它可以包含表达式和语句,并且可用于创建委托或表达式目录树类型。

所有 Lambda 表达式 都使用 Lambda 运算符 =>;,该运算符读为“goes to”。Lambda 表达式 x => x * x 读作“x goes to x times x”。

该 Lambda 运算符的前面是输入参数(如果有),后面包含表达式或语句块(函数体)。你可以把它当作一个函数。

举例说明:

Func<int, int, int> Add = (x, y) => x + y;
Console.WriteLine(Add(2, 3)); // 5

=> 运算符具有与赋值运算符 (=) 相同的优先级,并且是右结合运算符。

.Net Core 过滤器

.Net MVC 常用的4种过滤器:

- Action过滤器(IActionFilter):行为过滤器,在Action执行之前和执行之后调用。
  Result过滤器(IResultFilter):结果过滤器,在结果之前和之后调用。
  Exception过滤器(IExceptionFilter):异常过滤器,在发生异常时调用。
  Authorization过滤器(IAuthorizationFilter): 权限效验过滤器,身份验证时调用。

行为结果过滤器IActionFilter使用方法:

创建一个过滤器类,继承抽象类ActionFilterAttribute(IActionFilter、IResultFilter这俩货都在抽象类中),

override重写其虚方法:执行节点如下

  • OnActionExecuted: 进入action后,返回result前
    OnActionExecuting: 进入action前
    OnActionExecutionAsync: 进入acion前(异步)
    OnResultExecuted: 返回result后
    OnResultExecuting: 返回result前
    OnResultExecutionAsync: 返回result前(异步)
using Microsoft.AspNetCore.Mvc.Filters;
using System;
using System.Threading.Tasks;

namespace Filter
{
    public class ActionFilter: ActionFilterAttribute
    {
        public override void OnActionExecuted(ActionExecutedContext context)
        {
            Console.WriteLine("进入action之后,返回result前");
        }
        public override void OnActionExecuting(ActionExecutingContext context)
        {
            Console.WriteLine("进入action之前");
        }
        public override void OnResultExecuted(ResultExecutedContext context)
        {
            Console.WriteLine("返回result之后");
        }
        public override void OnResultExecuting(ResultExecutingContext context)
        {
            Console.WriteLine("返回result之前");
        }
        /*
        public override Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
        {
            Console.WriteLine("进入action之前(异步)");

            return Task.CompletedTask;
        }
        public override Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
        {
            Console.WriteLine("返回result之前(异步)");

            return Task.CompletedTask;
        }
        */

    }
}

ASP.NET Core MVC 过滤器简单使用:https://www.cnblogs.com/besos/p/14066947.html

token过滤器

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
 
namespace ModelFilter.Filter
{
    public class TokenHelper : IAuthorizationFilter
    {
        public void OnAuthorization(AuthorizationFilterContext filterContext)
        {
            string token = filterContext.HttpContext.Request.Headers["Authorization"];
            if (string.IsNullOrWhiteSpace(token))
            {
 
                filterContext.Result = new JsonResult(new { ReturnCode = 0, Message = "身份凭证已过期,请重新登录" });
                return;
            }
 
            if (token.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
            {
                token = token.Remove(0, 7);
            }
           // 获得token  进行简单验证
           // if ......
                       
        }
    }
}
//os:这个逻辑和我的案例很像

这个写得好:https://www.freesion.com/article/6699137790/

Asp.Net Core MVC框架默认实现的过滤器的执行顺序:

img

Authorization Filters:身份验证过滤器,处在整个过滤器通道的最顶层。对应的类型为: AuthorizeAttribute.cs

Resource Filters:资源过滤器。因为所有的请求和响应都将经过这个过滤器,所以在这一层可以实现类似缓存的功能。对应的接口有同步和异步两个版本: IResourceFilter.cs 、 IAsyncResourceFilter.cs

Action Filters:方法过滤器。在控制器的Action方法执行之前和之后被调用,一个很常用的过滤器。对应的接口有同步和异步两个版本: IActionFilter.cs 、 IAsyncActionFilter.cs

Exception Filters:异常过滤器。当Action方法执行过程中出现了未处理的异常,将会进入这个过滤器进行统一处理,也是一个很常用的过滤器。对应的接口有同步和异步两个版本: IExceptionFilter.cs 、 IAsyncExceptionFilter.cs

Result Filters:返回值过滤器。当Action方法执行完成的结果在组装或者序列化前后被调用。对应的接口有同步和异步两个版本: IResultFilter.cs 、 IAsyncResultFilter.cs

展示了每种过滤器的实现方式,以及它们的执行顺序。

我主要学习过滤器的引用

首先自定义一个ActionFilter

 1 using System;
 2 using Microsoft.AspNetCore.Mvc.Filters;
 3 
 4 namespace WebApiFrame.Core.Filters
 5 {
 6     public class MyActionFilterAttribute : Attribute, IActionFilter
 7     {
 8         public void OnActionExecuted(ActionExecutedContext context)
 9         {
10 
11         }
12 
13         public void OnActionExecuting(ActionExecutingContext context)
14         {
15             context.HttpContext.Response.Headers.Add("My-Header", "WebApiFrame-Header");
16         }
17     }
18 }

一、作为特性标识引用

标识在控制器上,则访问这个控制器下的所有方法都将调用这个过滤器

 1 using System;
 2 using Microsoft.AspNetCore.Mvc;
 3 using Microsoft.Extensions.Logging;
 4 using WebApiFrame.Core.Filters;
 5 using WebApiFrame.Models;
 6 
 7 namespace WebApiFrame.Controllers
 8 {
 9 
10     [Route("api/[controller]")]
11     [MyActionFilter]
12     public class UsersController : Controller
13     {
14         private ILogger<UsersController> _logger;
15 
16         public UsersController(ILogger<UsersController> logger)
17         {
18             _logger = logger;
19         }
20 
21         [HttpGet]
22         public IActionResult GetAll()
23         {
24             throw new Exception("GetAll function failed!");
25         }
26 
27         [HttpGet("{id}")]
28         public IActionResult Get(int id)
29         {
30             var user = new User() { Id = id, Name = "Name:" + id, Sex = "Male" };
31             return new ObjectResult(user);
32         }
33 
34         #region 其他方法
35         // ......
36         #endregion
37     }
38 }

也可以标识在方法上,则只有被标识的方法被调用时才会调用过滤器

二、全局过滤器

使用了全局过滤器后,所有的控制器下的所有方法被调用时都将调用这个过滤器。

两种形式注册过滤器:

 1 using Microsoft.AspNetCore.Builder;
 2 using Microsoft.Extensions.DependencyInjection;
 3 using Microsoft.Extensions.Logging;
 4 using WebApiFrame.Core.Filters;
 5 
 6 namespace WebApiFrame
 7 {
 8     public class Startup
 9     {
10         public void ConfigureServices(IServiceCollection services)
11         {
12             // 注入MVC框架
13             services.AddMvc(options =>
14             {
15                 // 1、实例注册
16                 //options.Filters.Add(new MyActionFilterAttribute());
17 
18                 // 2、类型注册
19                 options.Filters.Add(typeof(MyActionFilterAttribute));
20             });
21         }
22 
23         public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
24         {
25             // 添加日志支持
26             loggerFactory.WithFilter(new FilterLoggerSettings()
27             {
28                 { "Microsoft", LogLevel.Warning } 
29             })
30             .AddConsole().AddDebug();
31 
32             // 添加NLog日志支持
33             //loggerFactory.AddNLog();
34 
35             // 添加MVC中间件
36             app.UseMvc();
37         }
38     }
39 }

三、通过SERVICEFILTER引用

通过在控制器或者Action方法上使用ServiceFilter特性标识引用过滤器。通过此方法可以将通过构造方法进行注入并实例化的过滤器引入框架内。

修改一下 MyActionFilterAttribute.cs 内容,添加一个带参数的构造方法,引入日志记录

 1 using System;
 2 using Microsoft.AspNetCore.Mvc.Filters;
 3 using Microsoft.Extensions.Logging;
 4 
 5 namespace WebApiFrame.Core.Filters
 6 {
 7     public class MyActionFilterAttribute : Attribute, IActionFilter
 8     {
 9         private readonly ILogger<MyActionFilterAttribute> logger;
10 
11         public MyActionFilterAttribute(ILoggerFactory loggerFactory)
12         {//here
13             logger = loggerFactory.CreateLogger<MyActionFilterAttribute>();
14         }
15 
16         public void OnActionExecuted(ActionExecutedContext context)
17         {
18 
19         }
20 
21         public void OnActionExecuting(ActionExecutingContext context)
22         {
23             context.HttpContext.Response.Headers.Add("My-Header", "WebApiFrame-Header");
24             logger.LogInformation("MyActionFilterAttribute Executiong!");
25         }
26     }
27 }

修改 Startup.cs 的ConfigureServices方法,将过滤器类型注入到DI(依赖注入)容器里

 1         public void ConfigureServices(IServiceCollection services)
 2         {
 3             // 注入MVC框架
 4             services.AddMvc(options =>
 5             {
 6                 // 实例注册
 7                 //options.Filters.Add(new MyActionFilterAttribute());
 8 
 9                 // 类型注册
10                 //options.Filters.Add(typeof(MyActionFilterAttribute));
11             });
12 
13             // 将过滤器类型添加到DI容器里
14             services.AddScoped<MyActionFilterAttribute>();
15         }

四、通过TYPEFILTER引入

再次修改 MyActionFilterAttribute.cs 的构造器方法,添加普通的参数

 1 using System;
 2 using Microsoft.AspNetCore.Mvc.Filters;
 3 using Microsoft.Extensions.Logging;
 4 
 5 namespace WebApiFrame.Core.Filters
 6 {
 7     public class MyActionFilterAttribute : Attribute, IActionFilter
 8     {
 9         private readonly string _key;
10         private readonly string _value;
11 
12         public MyActionFilterAttribute(string key, string value)
13         {
14             _key = key;
15             _value = value;
16         }
17 
18         public void OnActionExecuting(ActionExecutingContext context)
19         {
20             context.HttpContext.Response.Headers.Add(_key, _value);
21         }
22         
23         public void OnActionExecuted(ActionExecutedContext context)
24         {
25             
26         }
27     }
28 }

在 UsersController.cs 控制器的Action方法上添加特性标识,同时注释掉 Startup.cs 的ConfigureServices的类型注入方法。因为用TypeFilter引用过滤器不需要将类型注入到DI容器

1         [HttpGet("{id}")]
2         [TypeFilter(typeof(MyActionFilterAttribute), Arguments = new object[]{ "My-Header", "WebApiFrame-Header" })]
3         public IActionResult Get(int id)
4         {
5             var user = new User() { Id = id, Name = "Name:" + id, Sex = "Male" };
6             return new ObjectResult(user);
7         }

另外,也可以通过TypeFilter引用需要通过构造方法注入进行实例化的过滤器。将上面第三点的例子里的进行改写

 1 using Microsoft.AspNetCore.Mvc;
 2 using Microsoft.AspNetCore.Mvc.Filters;
 3 using Microsoft.Extensions.Logging;
 4 
 5 namespace WebApiFrame.Core.Filters
 6 {
 7     public class MyActionFilterAttribute : TypeFilterAttribute
 8     {
 9         public MyActionFilterAttribute() : base(typeof(MyActionFilterImpl))
10         {
11 
12         }
13 
14         private class MyActionFilterImpl : IActionFilter
15         {
16             private readonly ILogger<MyActionFilterImpl> logger;
17 
18             public MyActionFilterImpl(ILoggerFactory loggerFactory)
19             {
20                 logger = loggerFactory.CreateLogger<MyActionFilterImpl>();
21             }
22 
23             public void OnActionExecuting(ActionExecutingContext context)
24             {
25                 context.HttpContext.Response.Headers.Add("My-Header", "WebApiFrame-Header");
26                 logger.LogInformation("MyActionFilterAttribute Executiong!");
27             }
28 
29             public void OnActionExecuted(ActionExecutedContext context)
30             {
31 
32             }
33         }
34     }
35 }

修改 UsersController.cs 控制器的Action方法的特性标识

1         [HttpGet("{id}")]
2         [MyActionFilter]
3         public IActionResult Get(int id)
4         {
5             var user = new User() { Id = id, Name = "Name:" + id, Sex = "Male" };
6             return new ObjectResult(user);
7         }

其它内容:

1、自定义过滤器执行顺序

以ActionFilter执行顺序为例,默认执行顺序如下:

1. Controller OnActionExecuting
2. Global OnActionExecuting
3. Class OnActionExecuting
4. Method OnActionExecuting
5. Method OnActionExecuted
6. Class OnActionExecuted
7. Global OnActionExecuted
8. Controller OnActionExecuted

2、过滤器与中间件

  1. 过滤器是MVC框架的一部分,中间件属于Asp.Net Core管道的一部分。

  2. 过滤器在处理请求和响应时更加的精细一些,在用户权限、资源访问、Action执行、异常处理、返回值处理等方面都能进行控制和处理。而中间件只能粗略的过滤请求和响应。

一个不错的博客:https://www.cnblogs.com/niklai/p/5655061.html

异常中间件

http://www.52codes.net/article/76797.html

if (env.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}

这样写入中间件,那么在env环境下就会去执行UseDeveloperExceptionPage

这个是dev环境下,那么生产环境不能直接给用户看到错误信息。

app.UseExceptionHandler("/error");

将错误转移到/error 处理

swagger

Swagger接口文档在.NET中的使用:https://blog.csdn.net/m0_37591671/article/details/102652935

Swagger工具,用于为ASP.NET Core后端接口生成漂亮的API文档,API文档是前后端开发人员联系的纽带。

本文采用Swagger版本及ASP.NET Core版本如下。

  • Swashbuckle.AspNetCore 版本号5.0.0-rc4
  • ASP.NET Core 版本号.Net Core 2.2

1、安装Swashbuckle.AspNetCore

控制台

Install-package Swashbuckle.AspNetCore -Version 5.0.0-rc4

Nuget包管理器

2、startup.cs 内添加Swagger服务

//添加Swagger生成器服务
services.AddSwaggerGen(c=> {
//添加Swagger文档
c.SwaggerDoc("v1",new Microsoft.OpenApi.Models.OpenApiInfo { Title = "My API", Version = "v1" });
            });
//ps:我的案例里这里写了很多,甚至还包含了c.

//巴拉巴拉

//3、添加Swagger到中间件
                app.UseSwagger();
                app.UseSwaggerUI(c =>
                {
                    c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");//Swagger文档路径
                    c.RoutePrefix = "";//设置为首页访问
                });

4、接口添加注释文档

1)生成xml文档

2)代码中配置

               services.AddSwaggerGen(c=> {
                //添加Swagger文档
                c.SwaggerDoc("v1",new Microsoft.OpenApi.Models.OpenApiInfo { Title = "My API", Version = "v1" });
                //
                c.CustomOperationIds(apiDesc =>
                {
                    return apiDesc.TryGetMethodInfo(out MethodInfo methodInfo) ? methodInfo.Name : null;
                });
                //接口添加注释
                var filePath = Path.Combine(System.AppContext.BaseDirectory, "Swaggger5Test.xml");
                c.IncludeXmlComments(filePath);

                //Model添加注释
                var modelPath = Path.Combine(System.AppContext.BaseDirectory, "Swagger5Test.Models.xml");
                c.IncludeXmlComments(modelPath);
            });

//5、添加过滤器
//添加Path过滤器,可以只显示以/api/Product的控制器的文档

                //过滤器
                app.UseSwagger(c => {
                    c.PreSerializeFilters.Add((swaggerDoc, httpReq) => {
                        OpenApiPaths paths = new OpenApiPaths();
                        foreach (var path in swaggerDoc.Paths)
                        {
                            if (path.Key.StartsWith("/api/Product"))//过滤Path
                                paths.Add(path.Key, path.Value);
                        }
                        swaggerDoc.Paths = paths;
                      
                        swaggerDoc.Servers = new List<OpenApiServer> { new OpenApiServer { Url = $"{httpReq.Scheme}://{httpReq.Host.Value}" } };
                    });

                });

6、设置首页访问

1)首先修改 swagger 的 launchSettings.json 文件

2)代码设置

                app.UseSwaggerUI(c =>
                {

                    c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");//Swagger文档路径
                    c.RoutePrefix = "";//设置为首页访问
                });

注:给接口添加注释之后,如果有的接口没有添加注释,就会生成警告,解决方式是在取消显示警告中添加1591

源码地址:https://github.com/zhongbodong/Swagger5Test


C#中使用swagger小技巧: https://wenku.baidu.com/view/53bb0644deccda38376baf1ffc4ffe473368fded.html

swaggerUl显示的接口内容主要用于开发阶段便于与前端联调,不适合发布到对外的站点。

两种方式,让接口不显示在SwaggerUl中

1、使用属性 [ApiExplorerSettings(lgnoreApi=true)]设置接口不显示到swaggerUl,此方法比较灵活,可以根据情况在对于的控制器或者API接口中添加。

[ApiExplorerSettings(IgnoreApi = true)
public class SystemController : ApiController
 {
   
 }
 //⬆️ 整个控制器中的接口都不会显示在swaggerUI,如果只是想针对某个接口不显示在UI中,可以在接口处增加属性
 public class SystemController : ApiController
 {
   [HttpGet]
   [ApiExplorerSettings(IgnoreApi = true)]
   public string GetSystem Time()
   {
     return Date Time.Now.ToString(;
   }
 }

调试一般都会将编译类型选择为Debug,那么可以如下写:

#if (!DEBUG)
  [ApiExplorerSettings(IgnoreApi = true)]
#endif
  public class SystemController ApiController
  {
    
  }
//这样开发阶段就可以正常的查看swaggerUI,到发布到UAT或者其他环境时,选择Release类型编译发布即可。
//针对某个接口不显示在UI中,可以在接口处修改属性

2、不加载SwaggerConfig文件,这样在发布就不会显示swaggerUl

#if (DEBUG)
[assembly: PreApplicationStartMethod(typeof(SwaggerConfig), "Register")]
#endif

这样做的缺点是,接口说明都不显示了。

vue

https://www.runoob.com/vue2/vuejs-ajax-axios.html

路由按需加载(路由懒加载)

https://blog.csdn.net/qq_46199553/article/details/120853124

https://www.jianshu.com/p/487a600824af/

vue-router 配置路由 , 使用vue的异步组件技术 , 可以实现按需加载

但是,这种情况下一个组件生成一个js文件

举例:异步加载组件,当你访问 / ,才会加载 home.vue

import 写法

{
  path: '/',
  // "@"相当于".."
  component: () => import('../pages/home.vue'),
  meta: {
    title: 'home'
  }
}

vue-router 中,require代替import解决vue项目首页加载时间过久的问题

vue 的路由配置文件(routers.js),一般使用import引入的写法,当项目打包时路由里的所有 component 都会打包在一个js中,在项目刚进入首页的时候,就会加载所有的组件,所以导致首页加载较慢,

而用require会将component分别打包成不同的js,按需加载,访问此路由时才会加载这个js,所以就避免进入首页时加载内容过多。

require写法

{
  // 进行路由配置,规定'/'引入到home组件
  path: '/',
  component: resolve => require(['../pages/home.vue'], resolve),
  meta: {
    title: 'home'
  }
}

require: 运行时调用,理论上可以运用在代码的任何地方,
import:编译时调用,必须放在文件开头

el-input

https://www.jb51.net/article/191749.htm

https://element.eleme.cn/#/zh-CN/component/input

Element Input输入框

https://blog.csdn.net/xie__jin__cheng/article/details/108832318

表单验证的三种方式:

1、表单上加 rules

<el-form class="apply-form first-form" :model="formData" :rules="rule" ref="form">
    <el-form-item label="姓名" prop="visitorName">
        <el-input v-model="formData.visitorName" placeholder="请输入姓名" clearable></el-input>
    </el-form-item>
    <el-form-item label="身份证号" prop="cardCode">
       <el-input v-model="formData.cardCode" :maxlength="18" placeholder="请输入身份证号" clearable></el-input>
    </el-form-item>
</el-form>
 
data() {
   return {
       formData: {
           visitorName: '',
           cardType: 1,
           cardCode: ''
       },
       rule: {
           visitorName: [
               { required: true, message: '请输入姓名', trigger: 'blur' },
               { min: 2, max: 10, message: '长度在 210 个字符', trigger: 'blur' },
               {
                        required: true,
                        pattern: /^[\u4e00-\u9fa5_a-zA-Z0-9.·-]+$/,
                        message: '姓名不支持特殊字符',
                        trigger: 'blur'
               }
          ],
          cardCode: [
              { required: true, message: '请输入身份证号', trigger: 'blur' },
              { min: 15, max: 18, message: '请如实填写18位号码,以供学校保卫科核对', trigger: 'blur' },
              {
                   required: true,
                   pattern: /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/,
                   message: '请输入正确的身份证号码',
                   trigger: 'blur'
              }
          ]
      }
   }
}


2、需要自定义校验规则的情况

3、在el-form-item单个添加

this.$emit()

https://blog.csdn.net/Zjx91919191/article/details/119918607

主要用于子组件向父组件传值

//slot:定义插槽
   Vue.component("today",{
            template: '<div>' +
                '<div>' +
                '<slot name="today-title"></slot>' +
                '<ul>' +
                '<slot name="today-items"></slot>' +
                '</ul>' +
                '</div>'
   });
   Vue.component("today-title",{
       props:['title'],
       template: '<div>{{title}}</div>'
   })
    Vue.component("today-items",{
        props: ['item'],
        //只能绑定当前的组件方法
        template: '<li>{{item}}</li>',

    })
  • 8
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值