在上一篇博客中的默认 ASP.NET Core Web 项目代码中都有看到一个 Startup
类,通过 UseStartup 方法指定该类。
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build()
.Run();
在上一篇博客中的默认 ASP.NET Core Web 项目代码中都有看到一个 Startup
类,通过 UseStartup 方法指定该类。
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build()
.Run();
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();
}
实体模型对应数据库中的表,相比普通的类,实体类一般都需要指定主键。在实体类中有三种方法可以指定主键。
名称为 Id 或者 类名 + Id 的属性会自动被识别为主键;
下面类中的 CarId 属性会自动识别为主键。
public class Car
{
public int CarId { get; set; }
public string Color { get; set; }
}
通过添加 [Key]
特性指定主键;
下面类中的 EmpIdentity 属性会被识别为主键。
public class Employee
{
[Key]
public int EmpIdentity { get; set; }
public int EmpAge { get; set; }
public int EmpName { get; set; }
}
通过在重写 DbContext
的 OnModelCreating 方法中调用 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));
}
}
ASP.NET Core 项目中的 “服务”,指的是 用于扩展应用程序功能的一系列类型。在应用程序初始化期间,会把需要的服务类型实例添加到 ServiceCollection
集合中,这些添加到集合中的服务实例将通过 依赖注入 提供给其他代码使用(例如可以注入控制器的构造函数中、或者 Startup
的 Configure 方法中)。
在之前的博客中提到过 Environment 环境变量有 3 个预定义的值,分别表示了常用的 3 种启动环境。
在哈希算法中 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
反射技术可以在应用程序运行阶段对程序进行解析,包括获取程序集中的类型、类型的成员列表、参数列表等信息,还可以创建类型实例或调用类型成员。
与反射有关的类型都位于 System.Reflection 命名空间下。
提供了一系列加载程序的静态方法,最常用的是 LoadFrom 静态方法,通过 dll 文件加载程序集,返回值是一个 Assembly
实例。
下面的代码示例通过 HttpWebRequest
和 HttpWebResponse
类实现图片下载功能。
HttpWebRequest
对象用于向服务器写数据HttpWebResponse
对象用于读取来自服务器的数据本节主要讲了基于 Socket 的网络通信。 Socket 支持很多网络协议,本节讲了一下几种通信方法:
TcpListener
和 TcpClient
类UdpClient
类序列化(Serialization 也叫“串行化”),就是将某个对象实例的状态信息存储到可传输介质中,例如内存中、文件中以及通过网络发送的数据中。实例的状态信息包括 对象的属性 和 字段成员的值(不包括方法和事件)。
从可传输介质中读取数据,重新为对象的属性或字段成员赋值的过程称为反序列化(Deserialization)。
命名管道是一种比较简单易用的通信方式,它支持同一台计算机上进程与进程之间,或者不同计算机上进程与进程之间的数据传输。
需要用到 System.IO.Pipes 命名空间下的两个类:
NamedPipeServerStream
类
通信中的服务器。
实例化时可以通过 direction 参数传入一个 PipeDirection
枚举来指定管道的通信方法。默认值为 PipeDirection.InOut 双向通信。
实例化后需要调用 WaitForConnection 或 WaitForConnectionAsync 方法来侦听客户端连接。
NamedPipeClientStream
类
通信中的客户端。
实例化时可以通过 serverName 参数指定远程计算机的名称。本机测试时可以指定为 . 或 localhost 。
实例化时可以通过 direction 参数传入一个 PipeDirection
枚举来指定管道的通信方法。默认值为 PipeDirection.InOut 双向通信。
实例化后需要调用 Connect 方法向服务器发起连接请求。
内存映射文件(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));
在 System.IO.Compression 命名空间下,.NET Core 框架已经封装了一组常用的类,用于对流进行压缩和解压缩。分别是
DeflateStream
ZipArchive
GZipStream
其中 DeflateStream
和 GZipStream
均继承自 System.IO.Stream 类。
流,是输入/输出操作中很常用的一种类型,它表示 数据内容的字节按照顺序进行排列。
内存流,即从内存中划分出一个特定区域,应用程序可以将字节序列存放到这个区域中。内存流很适合用于读写临时数据。
MemoryStream
类封装了一系列操作内存流的方法。
操作目录与文件主要使用如下几个类:
Directory
:提供了一系列操作目录的静态方法。File
:提供了一系列操作文件的静态方法。DirectoryInfo
:提供了一系列操作目录的实例方法。FileInfo
:提供了一系列操作文件的实例方法。DriveInfo
:磁盘信息使用 dynamic
关键字可以用来声明动态类型。ExpandoObject
是专为动态类型封装的类型。
dynamic dx = new System.Dynamic.ExpandoObject();
dx.Message = "Hello";
dx.Name = "World";
Console.WriteLine($"{dx.Message}, {dx.Name}!"); // Hello, World!
开启 LINQ 查询的并行模式,只需在原序列上调用 AsParallel 扩展方法,但是不应该滥用并行模式。
满足以下条件的查询,可以考虑以并行模式执行:
where
与 select
子句上需要额外的处理工作(例如要转换类型)元组(Tuple
)类型很早就有了,但在低于 7.0 的版本中,只能通过 Item1、Item2 这样的属性来访问,没有实际的语义。
从 7.0 开始元组增加了语义上的支持等功能,更加易于使用。实际上这里说的元组已经不是指之前的 Tuple
类型了,而是新增的 值元组 ValueTuple
类型。
BlockingCollection<T>
类似于 ConcurrentBag<T>
,也是一个用于多线程访问的集合类,但是功能上要强大很多。
BlockingCollection<T>
本身实现了类似于消息队列(MQ)的生产者 - 消费者模式。
可以设置集合的容量上限。
只能在创建实例时设置。
通过 BoundedCapacity 只读属性可以获取其容量上限。
若未指定,则默认值为 int.MaxValue,此时 BoundedCapacity 属性值为 -1。
集合为空时移除(Take)操作会被阻塞,集合满时新增(Add)操作会被阻塞。
消费者可以阻塞处理,直到生产者新增。
而当消费者消费过慢导致集合堆积达到容量上限时,会阻塞新增操作,直到消费者消费后释放了容量空间。
如果不想新增和移除处理被阻塞,BlockingCollection<T>
也提供了 TryAdd 和 TryTake 方法。
生产者可以调用 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
取消 Take、Add 等操作。
所有 BlockingCollection<T>
的操作都有带 CancellationToken
参数的重载。
取消时会触发 OperationCanceledException
异常,需要时可以手动捕捉该异常来响应取消请求。
ConcurrentBag<T>
特点:泛型集合、无序、线程安全。
泛型集合
Add
方法添加元素TryTake
方法取出元素然后从集合中删除该元素TryPeek
方法取出元素但不会从集合中删除该元素IsEmpty
属性表示集合是否是空集合无序
从集合中取出元素的顺序和放入的顺序 可能 不一致。
线程安全
ConcurrentBag<T>
的优点就是可以在多个线程上都可以访问,而且是线程安全的。
后面的几个示例就是为了验证这一点,并且另外使用 List<T>
和 Queue<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);
}
特性是一种比较特殊的类,通常作为代码对象的附加部分,用于向 CLR 提供一些补充信息。
特性在项目中应用很广,比如 Web 服务(ASMX) 中一般都会用到如下特性:
[WebMethod(Description = "", EnableSession = false)]
[ScriptMethod(UseHttpGet = false)]
枚举是值类型。通过 enum
关键字声明枚举。
enum Options
{
OneWay = 1,
TwoWay = 2,
MixWay = 3,
}
显示实现接口就是在实现接口的成员前面加上接口的名称。这种方法可以有效解决接口成员冲突问题。
类是可以继承多个接口的,当接口中有方法声明相同时,就需要显示的实现接口。
void IDownloader.Start()
{
}
使用 override
关键字可以重写基类成员,这是对基类成员的扩展。如要彻底替换基类成员则需要使用 new
关键字。
// 基类方法
public void Run()
// 派生类替换
public new void Run()
析构函数(destructor)与构造函数(constructor)的作用正好相反。
构造函数在创建对象实例时调用,用于对类型成员初始化;
析构函数在对象实例即将被回收时执行,可用于一些清理工作。
析构函数以 ~
开头,无返回值,无参数,~
之后紧跟类名(无空格)。
class Test {
~Test() {
}
}
事件(event
)是类型中的一种成员对象,它是委托(delegate
)类型。主要是运用了委托可以绑定一个或多个方法的特点。关于链式委托可以参考一下这篇博客。
声明事件需要用到 event 关键字,自定义 add 和 remove 访问器方法。
开发时一般都是通过 Visual Studio 来创建和运行程序,不过了解一下常用 .NET Core 的命令行工具还是有必要的。特别是编译和运行命令,偶尔会使用到。
dotnet new
下面的命令会创建一个控制台项目,项目名默认和当前文件夹同相同。
dotnet new console