.NET Core 实战 [No.330~332] Startup

上一篇博客中的默认 ASP.NET Core Web 项目代码中都有看到一个 Startup 类,通过 UseStartup 方法指定该类。

WebHost.CreateDefaultBuilder(args)
    .UseStartup<Startup>()
    .Build()
    .Run();
.NET Core 实战 [No.326~329] Web 主机配置

默认配置

ASP.NET Core Web 应用程序 项目创建时,已经提供了一些默认配置。

随着版本的升级,创建 WebHost 的方式也一直在更新。 .NET Core 1.1 时默认的写法如下:

public static void Main(string[] args)
{
    var host = new WebHostBuilder()
        .UseKestrel()
        .UseContentRoot(Directory.GetCurrentDirectory())
        .UseIISIntegration()
        .UseStartup<Startup>()
        .UseApplicationInsights()
        .Build();

    host.Run();
}
.NET Core 实战 [No.377~378] 迁移实体并生成数据库

实体模型

实体模型对应数据库中的表,相比普通的类,实体类一般都需要指定主键。在实体类中有三种方法可以指定主键。

  1. 名称为 Id 或者 类名 + Id 的属性会自动被识别为主键;

    下面类中的 CarId 属性会自动识别为主键。

    public class Car
    {
        public int CarId { get; set; }
        public string Color { get; set; }
    }
    
  2. 通过添加 [Key] 特性指定主键;

    下面类中的 EmpIdentity 属性会被识别为主键。

    public class Employee
    {
        [Key]
        public int EmpIdentity { get; set; }
        public int EmpAge { get; set; }
        public int EmpName { get; set; }
    }
    
  3. 通过在重写 DbContextOnModelCreating 方法中调用 HasKey 方法指定主键。

    public class Activity
    {
        public Guid ActFlag { get; set; }
        public TimeSpan Period { get; set; }
    }
    

    OnModelCreating 方法中指定 ActFlag 为主键。

    public class MyDbContext : DbContext
    {
        public DbSet<Car> Cars { get; set; }
        public DbSet<Employee> Employees { get; set; }
        public DbSet<Activity> Activities { get; set; }
    
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Activity>().HasKey(nameof(Activity.ActFlag));
        }
    }
    
.NET Core 实战 [No.335~346] 依赖注入与中间件

服务

ASP.NET Core 项目中的 “服务”,指的是 用于扩展应用程序功能的一系列类型。在应用程序初始化期间,会把需要的服务类型实例添加到 ServiceCollection 集合中,这些添加到集合中的服务实例将通过 依赖注入 提供给其他代码使用(例如可以注入控制器的构造函数中、或者 StartupConfigure 方法中)。

.NET Core 实战 [No.333~334] 启动环境

之前的博客中提到过 Environment 环境变量有 3 个预定义的值,分别表示了常用的 3 种启动环境。

  • Development:开发环境
  • Staging:预览环境
  • Production:生产环境
.NET Core 实战 [No.321~325] 加密算法

MD5

在哈希算法中 MD5 是最常见的,多用于校验密码。一般做法是,先用密码字符串计算出 MD5 值,再把 MD5 值转换为字符串,存进数据库。

Console.Write("请输入文本:");
string input = Console.ReadLine();
byte[] data = Encoding.UTF8.GetBytes(input);
MD5 md5 = MD5.Create();
byte[] result = md5.ComputeHash(data);
Console.WriteLine(BitConverter.ToString(result).Replace("-", string.Empty).ToLower());
// 请输入文本:佳佳的博客
// 0ee5d025905e0e602d8ee997dc1f128
.NET Core 实战 [No.306~312] 反射

反射技术可以在应用程序运行阶段对程序进行解析,包括获取程序集中的类型、类型的成员列表、参数列表等信息,还可以创建类型实例或调用类型成员。

与反射有关的类型都位于 System.Reflection 命名空间下。

1. Assembly

提供了一系列加载程序的静态方法,最常用的是 LoadFrom 静态方法,通过 dll 文件加载程序集,返回值是一个 Assembly 实例。

.NET Core 实战 [No.304~305] Http 编程

HttpWebRequest & HttpWebResponse

下面的代码示例通过 HttpWebRequestHttpWebResponse 类实现图片下载功能。

  • HttpWebRequest 对象用于向服务器写数据
  • HttpWebResponse 对象用于读取来自服务器的数据
.NET Core 实战 [No.301~303] Socket 通信

本节主要讲了基于 Socket 的网络通信。 Socket 支持很多网络协议,本节讲了一下几种通信方法:

  • 基于 TCP 协议的 Socket 通信
  • 封装了 TCP 协议的 TcpListenerTcpClient
  • 封装了 UDP 协议的 UdpClient
.NET Core 实战 [No.267~283] 序列化

序列化Serialization 也叫“串行化”),就是将某个对象实例的状态信息存储到可传输介质中,例如内存中、文件中以及通过网络发送的数据中。实例的状态信息包括 对象的属性字段成员的值不包括方法和事件)。

从可传输介质中读取数据,重新为对象的属性或字段成员赋值的过程称为反序列化Deserialization)。

.NET Core 实战 [No.265~266] 命名管道

命名管道是一种比较简单易用的通信方式,它支持同一台计算机上进程与进程之间,或者不同计算机上进程与进程之间的数据传输。

需要用到 System.IO.Pipes 命名空间下的两个类:

  1. NamedPipeServerStream

    通信中的服务器。
    实例化时可以通过 direction 参数传入一个 PipeDirection 枚举来指定管道的通信方法。默认值为 PipeDirection.InOut 双向通信。
    实例化后需要调用 WaitForConnectionWaitForConnectionAsync 方法来侦听客户端连接。

  2. NamedPipeClientStream

    通信中的客户端。
    实例化时可以通过 serverName 参数指定远程计算机的名称。本机测试时可以指定为 .localhost
    实例化时可以通过 direction 参数传入一个 PipeDirection 枚举来指定管道的通信方法。默认值为 PipeDirection.InOut 双向通信。
    实例化后需要调用 Connect 方法向服务器发起连接请求。

.NET Core 实战 [No.263~264] 内存映射文件

内存映射文件(MemoryMappedFile),其实是在应用程序内存空间中划分的一块特殊内存,可以像操作磁盘文件那样,在内存中新建文件,并写入或读取内容。

内存映射文件可以用于在进程之间共享数据。下面的两段代码处于两个不同的项目中,分别用于写入和读取内存映射文件。

MemoryMappedFile file = MemoryMappedFile.CreateNew("test", 200L);
using (MemoryMappedViewStream mmvstream = file.CreateViewStream())
using (StreamWriter writer = new StreamWriter(mmvstream))
{
    writer.WriteLine("Hi. Welcome to my blog<www.liujiajia.me>.");
}
Console.WriteLine("文件已写入内存");
Thread.Sleep(TimeSpan.FromSeconds(10));
.NET Core 实战 [No.260~262] 压缩与解压缩

System.IO.Compression 命名空间下,.NET Core 框架已经封装了一组常用的类,用于对流进行压缩和解压缩。分别是

  • DeflateStream
  • ZipArchive
  • GZipStream

其中 DeflateStreamGZipStream 均继承自 System.IO.Stream 类。

.NET Core 实战 [No.253~259] 流

,是输入/输出操作中很常用的一种类型,它表示 数据内容的字节按照顺序进行排列

内存流(MemoryStream

内存流,即从内存中划分出一个特定区域,应用程序可以将字节序列存放到这个区域中。内存流很适合用于读写临时数据。

MemoryStream 类封装了一系列操作内存流的方法。

.NET Core 实战 [No.241~252] 目录与文件

操作目录与文件主要使用如下几个类:

  • Directory:提供了一系列操作目录的静态方法。
  • File:提供了一系列操作文件的静态方法。
  • DirectoryInfo:提供了一系列操作目录的实例方法。
  • FileInfo:提供了一系列操作文件的实例方法。
  • DriveInfo:磁盘信息
.NET Core 实战 [No.236~240] 动态类型

使用 dynamic 关键字可以用来声明动态类型。ExpandoObject 是专为动态类型封装的类型。

dynamic dx = new System.Dynamic.ExpandoObject();

dx.Message = "Hello";
dx.Name = "World";

Console.WriteLine($"{dx.Message}, {dx.Name}!"); // Hello, World!
.NET Core 实战 [No.232] 使用并行 LINQ

开启 LINQ 查询的并行模式,只需在原序列上调用 AsParallel 扩展方法,但是不应该滥用并行模式。

满足以下条件的查询,可以考虑以并行模式执行:

  1. 序列中数据量很大
  2. LINQ 查询中 whereselect 子句上需要额外的处理工作(例如要转换类型)
  3. 对产生的结果没有严格的顺序要求(尽管并行查询可以调用 AsOrdered 扩展方法来维持序列的顺序,但在一定程度上会降低性能,仅在必要时使用)
.NET Core 实战 [No.201~207] 元组

元组(Tuple)类型很早就有了,但在低于 7.0 的版本中,只能通过 Item1Item2 这样的属性来访问,没有实际的语义。

7.0 开始元组增加了语义上的支持等功能,更加易于使用。实际上这里说的元组已经不是指之前的 Tuple 类型了,而是新增的 值元组 ValueTuple 类型。

.NET Core 实战 [No.200] 跨线程访问 BlockingCollection 集合

BlockingCollection<T> 类似于 ConcurrentBag<T> ,也是一个用于多线程访问的集合类,但是功能上要强大很多。

  • BlockingCollection<T> 本身实现了类似于消息队列(MQ)的生产者 - 消费者模式

  • 可以设置集合的容量上限

    只能在创建实例时设置。
    通过 BoundedCapacity 只读属性可以获取其容量上限。
    若未指定,则默认值为 int.MaxValue,此时 BoundedCapacity 属性值为 -1

  • 集合为空时移除(Take)操作会被阻塞,集合满时新增(Add)操作会被阻塞。

    消费者可以阻塞处理,直到生产者新增。
    而当消费者消费过慢导致集合堆积达到容量上限时,会阻塞新增操作,直到消费者消费后释放了容量空间。

    如果不想新增和移除处理被阻塞,BlockingCollection<T> 也提供了 TryAddTryTake 方法。

    生产者可以调用 CompleteAdding 方法来标记生产已结束,此时消费者可以根据 IsCompleted 来判断是否所有的项都已被消费完毕。

  • 封装实现了 IProducerConsumerCollection<T> 的任何集合类型。

    上一篇博客中介绍了 ConcurrentBag<T>,并在最后提到了 ConcurrentQueue<T>ConcurrentStack<T> 类,这三种类型都实现了 IProducerConsumerCollection<T> 接口。

    可在创建实例时指定封装的集合类型,默认为 ConcurrentQueue<T> 类型。

    BlockingCollection<string> bc = new BlockingCollection<string>(new ConcurrentBag<string>(), 1000);  
    
  • 支持使用 CancellationToken 取消 TakeAdd 等操作。

    所有 BlockingCollection<T> 的操作都有带 CancellationToken 参数的重载。
    取消时会触发 OperationCanceledException 异常,需要时可以手动捕捉该异常来响应取消请求。

.NET Core 实战 [No.199] 多个 Task 同时操作 ConcurrentBag 集合

ConcurrentBag<T> 特点:泛型集合无序线程安全

  • 泛型集合

    • Add 方法添加元素
    • TryTake 方法取出元素然后从集合中删除该元素
    • TryPeek 方法取出元素但不会从集合中删除该元素
    • IsEmpty 属性表示集合是否是空集合
  • 无序

    从集合中取出元素的顺序和放入的顺序 可能 不一致。

  • 线程安全

    ConcurrentBag<T> 的优点就是可以在多个线程上都可以访问,而且是线程安全的。
    后面的几个示例就是为了验证这一点,并且另外使用 List<T>Queue<T> 来做对比。

.NET Core 实战 [No.198] 使用 `Span<T>` 提升处理字符串的性能

Span<T> 用于操作各种 连续分布的内存数据 。可以通过以下来源初始化 Span<T> :

  • 常见的托管数组

    Span<char> span = str.ToCharArray();
    
  • 栈内存中的对象

    Span<int> arr = stackalloc [] {1, 2, 3};
    
  • 本地内存指针

    // 从进程的未托管内存中分配 100 字节的内存
    IntPtr native = Marshal.AllocHGlobal(100);
    Span<byte> nativeSpan;
    unsafe
    {
        nativeSpan = new Span<byte>(native.ToPointer(), 100);
    }
    
.NET Core 实战 [No.94~98] 特性

特性是一种比较特殊的类,通常作为代码对象的附加部分,用于向 CLR 提供一些补充信息。

特性在项目中应用很广,比如 Web 服务(ASMX) 中一般都会用到如下特性:

[WebMethod(Description = "", EnableSession = false)]
[ScriptMethod(UseHttpGet = false)]
.NET Core 实战 [No.88~93] 枚举

枚举是值类型。通过 enum 关键字声明枚举。

enum Options
{
    OneWay = 1,
    TwoWay = 2,
    MixWay = 3,
}
.NET Core 实战 [No.84] 显式实现接口

显示实现接口就是在实现接口的成员前面加上接口的名称。这种方法可以有效解决接口成员冲突问题。

类是可以继承多个接口的,当接口中有方法声明相同时,就需要显示的实现接口。

void IDownloader.Start()
{
}
.NET Core 实战 [No.77] 彻底替换基类的成员

使用 override 关键字可以重写基类成员,这是对基类成员的扩展。如要彻底替换基类成员则需要使用 new 关键字。

// 基类方法
public void Run()
// 派生类替换
public new void Run()
.NET Core 实战 [No.82] 析构函数

析构函数(destructor)与构造函数(constructor)的作用正好相反。

构造函数在创建对象实例时调用,用于对类型成员初始化;
析构函数在对象实例即将被回收时执行,可用于一些清理工作。

析构函数以 ~ 开头,无返回值,无参数,~ 之后紧跟类名(无空格)。

class Test {
    ~Test() {

    }
}
.NET Core 实战 [No.71] 封装事件

事件(event)是类型中的一种成员对象,它是委托(delegate)类型。主要是运用了委托可以绑定一个或多个方法的特点。关于链式委托可以参考一下这篇博客

声明事件需要用到 event 关键字,自定义 addremove 访问器方法。

.NET Core 实战 [No.7~13] 命令行工具

开发时一般都是通过 Visual Studio 来创建和运行程序,不过了解一下常用 .NET Core 的命令行工具还是有必要的。特别是编译和运行命令,偶尔会使用到。

1. dotnet new

下面的命令会创建一个控制台项目,项目名默认和当前文件夹同相同。

dotnet new console