宝塔面板建站后如何本地调试 - 问答 - 亿速云

mikel阅读(114)

来源: 宝塔面板建站后如何本地调试 – 问答 – 亿速云

使用宝塔建站后需要在本地实现调试,要在本机hosts文件中进行配置

1.在本机上使用记事本打开hosts文件,hosts文件路径:C:\Windows\System32\Drivers\etc;

2.hosts文件打开后,在文件最后面添加宝塔建站的网站域名,如127.0.0.1 www.123.com,保存即可;

 

购买使用亿速云服务器,可以极大降低初创企业、中小企业以及个人开发者等用户群体的整体IT使用成本,无需亲自搭建基础设施、简化了运维和管理的日常工作量,使用户能够更专注于自身的业务发展和创新。

linux宝塔面板安装composer的方法[全网详解]_宝塔 composer-CSDN博客

mikel阅读(101)

来源: linux宝塔面板安装composer的方法[全网详解]_宝塔 composer-CSDN博客

概念:
Composer是一个PHP依赖管理器,可以方便地管理和安装PHP项目所依赖的库和软件包。它可以自动下载、安装和更新这些库,同时确保它们与项目的其他部分兼容。Composer可以通过一个名为composer.json的配置文件来配置,该文件列出了项目所依赖的库、版本等信息。Composer还可以从Packagist等仓库中查找和下载各种PHP库和软件包。由于Composer的出现,开发人员不再需要手动下载、安装和配置PHP库,从而大大简化了PHP项目的开发和维护过程。

有些项目安装会用到composer,如果直接安装会报错“Warning: putenv() has been disabled”。

 

所以宝塔面板安装composer我们需要删除禁用函数,删除之后,才可以正常安装。下面就说说,宝塔面板linux版本如何安装composer!

1、删除禁用函数

宝塔面板默认禁用一些安装 Composer 要用到的 3 个函数如下:

putenv() 、 pcntl_signal() 、 proc_open(),

“PHP管理”→“禁用函数”→“删除putenv”。

 

这样删除就可以了。其他诸如此类,这里不再赘述。

2、全局安装

1)安装之前更新服务器软件包

如果是centos系统,可以使用 SSH 执行下方命令:

yum update -y
如果是debian类的

apt update
删除禁用函数和更新软件包后,就可以愉快安装composer了。这里用全局安装,安装命令如下:

cd ~
php -r “copy(‘https://install.phpcomposer.com/installer’, ‘composer-setup.php’);” # 将安装脚本下载到当前目录
php composer-setup.php # 运行安装脚本
php -r “unlink(‘composer-setup.php’);” # 删除安装脚本
mv composer.phar /usr/local/bin/composer # 全局安装 composer(配置系统环境变量)

如果是国内服务器,可以会下载缓慢,可以换源解决。将 composer 源改成阿里云的镜像

composer config repo.packagist composer https://mirrors.aliyun.com/composer/

安装完成后,可以用命令:“composer –version”来查看你安装的版本号。

 

3、局部安装

这里简单提一下,上述下载Composer的过程正确执行完毕后(最后一步不要执行),可以将composer.phar文件复制到任意目录(比如项目根目录下),然后通过php composer.phar指令即可使用Composer了

不过还是推荐用全局安装~~~

4、更换镜像源

随着Composer的发展,已经很多大厂商都加入了Composer的镜像源,强烈推荐使用阿里云的,同步频率快,速度也很稳定,不过你可能对其他的也有兴趣:

阿里云Composer镜像
https://mirrors.aliyun.com/composer/
腾讯云Composer镜像
PHP Composer
华为云Composer镜像
https://repo.huaweicloud.com/repository/php/
安畅云Composer镜像
https://php.cnpkg.org
5、最后

宝塔安装composer会报错的处理方法,大概就是这样了 。如果是lnmp环境那么需要编辑PHP配置文件:

vi /usr/local/php/etc/php.ini进入编辑状态。

输入/,进入搜索模式,找到disable_functions

寻找disable_functions字符串,将后面的putenv删除。
————————————————
版权声明:本文为CSDN博主「奔跑的蜗牛.」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/MrWangisgoodboy/article/details/129929934

.net 温故知新:【10】.NET ORM框架EFCore使用入门之CodeFirs、DBFirst - XSpringSun - 博客园

mikel阅读(93)

来源: .net 温故知新:【10】.NET ORM框架EFCore使用入门之CodeFirs、DBFirst – XSpringSun – 博客园

前言:本系列是我自己学习.net相关知识,以便跟上.net跨平台的步伐,目前工作原因基本在.net Framework4.7以下,所以才有了这一系列的学习总结,但是并不是从基本的C#语法和基础知识开始的,而是围绕.net core以后平台的重要设计和差异进行温故知新。目的在于通过要点的梳理最后串联起整个跨平台框架。之前的几篇算是把框架重要设计和框架重要知识点复习了,当然什么系统都可能使用到ORM框架。所以这里为了整个过程的完整连续性加入一个EFCore的示例,ORM不算详细写了,毕竟ORM框架可以根据需求选择很多,如果再详细那又是另外一个系列了,这里只做简单介绍。从这篇ORM完成之后就将进入ASP.NET core的学习总结!

EFCore

Entity Framework Core (EF Core) 是适用于 .NET 的新式对象数据库映射器。 它支持 LINQ 查询、更改跟踪、更新和架构迁移。

EF Core 通过数据库提供程序插件模型与 SQL Server/Azure SQL 数据库、SQLite、Azure Cosmos DB、MySQL、PostgreSQL 和更多数据库配合使用。

EFCore入门

在上面的EFCore介绍中我们又看到了提供程序描述,之前文章多次提到这个提供程序是.net框架中随处可见的,也就是通过这些不同的提供程序实现扩展和适配。本文我用两种数据库,sqlite和SQLServer并分别用code-first(代码优先)模式和db-frist(数据库优先)模式演示EFCore的使用。

1、Code First

新建一个.net 6.0 控制台应用程序,安装nuget包(EFCore的sqlite提供程序):

Install-Package Microsoft.EntityFrameworkCore.Sqlite

重要依赖Package Microsoft.EntityFrameworkCore包会自动安装。

编写SqliteContext类构成模型的上下文类,实体类:Student、Course。

namespace EFCoreDemo.Sqlite
{
    public class SqliteContext : DbContext
    {
        public DbSet<Student> Students { get; set; }
        public DbSet<Course> Courses { get; set; }

        /// <summary>
        /// sqlite 数据库文件路径
        /// </summary>
        public string DbPath { get; }

        public SqliteContext()
        {
            var folder = Environment.CurrentDirectory;
            DbPath = System.IO.Path.Join(folder, "CodeFirst.db");
        }

        protected override void OnConfiguring(DbContextOptionsBuilder options)
            => options.UseSqlite($"Data Source={DbPath}");
    }

    /// <summary>
    /// 学生
    /// </summary>
    public class Student
    {
        public int id { get; set; }
        public string name { get; set; }

        public List<Course> courses { get; set; }
    }

    /// <summary>
    /// 课程
    /// </summary>
    public class Course
    {
        public int id { get; set; }

        public string name { get; set; }
    }
}

接着我们安装包Microsoft.EntityFrameworkCore.Tools用来生成数据库

Install-Package Microsoft.EntityFrameworkCore.Tools

然后在“包管理器控制台(PMC)”中使用命令:

Add-Migration InitialCreate

提示创建成功:

PM> Add-Migration InitialCreate
Build started...
Build succeeded.
To undo this action, use Remove-Migration.
PM> Update-Database
Build started...
Build succeeded.
Applying migration '20221130040124_InitialCreate'.
Done.

搭建基架,为模型创建一组初始表。该命令完成后会生成一个Migration文件夹包含两个类,一个数据库创建类InitialCreate是我们定义的,可以按需求更改名称。一个模型快照类SqliteContextModelSnapshot上面的[DbContext(typeof(SqliteContext))]属性标识类所属的DbContext。使用该atrribute确定迁移应用于哪个上下文。

由于这是项目的第一次迁移,如果我们修改了模型后再Add-Migration生成的时候EFCore 会在添加列之前将更新的模型与旧模型的快照进行比较。基于该比较,EF Core 检测变化,并添加适当的迁移而不是再重新生成数据库。

image

最后运行命令Update-Database生成数据库和表,数据库在我们配置的程序路径下。

PM> Update-Database
Build started...
Build succeeded.
Applying migration '20221130040124_InitialCreate'.
Done.

image

生成的sqlite数据库如图,因为我们定义了Student、Course实体,所以会生成两个表以及表的字段,同时还会生成一个历史表,用于记录我们每次迁移的记录。另外在Student类里面我们定义了一个public List<Course> courses { get; set; } 属性,表示学生拥有哪些课程,这样相当于是一个外键关系,EFCore会为我们在Coures表里面创建一个Sudentid的外键来表达关联关系。同时我们查询学生的话理论上也能查出学生拥有的课程,接下来我们向数据库中插入数据并进行查询。

注意在命令生成的时候CurrentDirectory是项目目录,我们运行的时候要把生成的CodeFirst.db复制到bin/Debug

        static void Main(string[] args)
        {
            //实例化context
            SqliteContext context = new SqliteContext();
            //添加数据
            for (int i = 0; i < 5; i++)
            {

                List<Course> courses = new()
                {
                    new Course()
                    {
                        name = "语文"+i
                    },
                     new Course()
                    {
                        name = "数学"+i
                    },
                };

                context.Students.Add(new Student()
                {
                    name = "学生" + i,
                    courses = courses
                });
                context.SaveChanges();
            }
            //查询
            var students = context.Students.Include(t=>t.courses).ToList();
            students.ForEach(e =>
            {
                Console.Write(e.name);
                e.courses.ForEach(e => { Console.Write(e.name); });
                Console.WriteLine();
            });

            var student = context.Students.SingleOrDefault(t => t.name=="学生3");
            Console.WriteLine($"id:{student.id},name:{student.name}");
            Console.ReadKey();
        }

image

查询如果要包含外键关联的数据,需要用Include方法。不然上面的结果第一次可以用,第二次就查询不出来,因为第一次的数据添加后会直接在上下文Context里面。

2、 DB Frist

首先我们建一个SQLServer数据库,然后反向建两张一样的表。

image

在项目中安装 Microsoft.EntityFrameworkCore.Design 的 nuget 包。
Install-Package Microsoft.EntityFrameworkCore.Design
由于上面我安装Microsoft.EntityFrameworkCore.Tools nuget包的时候依赖项已经安装了所以就不需要再安装了。

然后安装sqlserver的提供程序 nuget 包。

Install-Package Microsoft.EntityFrameworkCore.SqlServer

image

安装好 nuget 包后在程序包管理器控制台里面使用命令:

Scaffold-DbContext 'Data Source=192.168.40.165;Initial Catalog=DBFirst;User Id=sa;Password=123456;Encrypt=False' -Context SqlServerContext    -OutputDir DBFirst Microsoft.EntityFrameworkCore.SqlServer
  • -Context:指定DbContext 类名称,如果不指定则是数据库名称+Context。
  • -OutputDir:指定生成的模型目录。

除此之外该命令还有其它参数包括:

  • -ContextDir:指定DbContext类生成目录。
  • -ContextNamespace:覆盖 DbContext 类的命名空间。
  • -Namespace: 覆盖所有输出类的命名空间。

生成的DBContext和模型:

image

使用SqlServerContext 查询数据:

            var students = context.Students.Include(t=>t.Courses).ToList();
            students.ForEach(e =>
            {
                Console.Write(e.Name);
                e.Courses.ToList().ForEach(e => { Console.Write("   "+e.Name); });
                Console.WriteLine();
            });

            var student = context.Students.SingleOrDefault(t => t.Name=="李四");
            Console.WriteLine($"id:{student.Id},name:{student.Name}");

image

EFCore的其它重要知识点

本篇作为入门使用,其它EFCode的重要知识点还是建议使用时候查看官方文档即可。
我认为EFCore的重要知识点包括但不限于:

  • EFCore中的约定大于配置,比如模型如果有Id字段默认就是主键。
  • 一对多关系配置和获取,上面示例中学生有哪些课程就是一对多,查询关联要用includ。
  • 多个外键字段的配置。
  • 一对多,多对多关系。
  • Iqueryable的作用和数据延迟加载,在我们查询数据的时候列表数据以Iqueryable类型返回,如果我们不是调用获取结果api,则不会立即查询数据库,而是等到使用tolist()、count()、max()等方法时才会查询数据返回结果。这样有利于我们在代码中复用Iqueryable,比如不同的if条件拼接后组成不同的查询语句。
  • EFcore 缓存。
  • AsNoTracking使用,EFCore默认会跟踪实体,也就是保存在内存中,用于更新删除等操作。如果只是查询数据而不用跟踪则使用AsNoTracking查询。
  • 并发控制。
    …..

.net 温故知新:【9】.NET日志记录 ILogger使用和原理 - XSpringSun - 博客园

mikel阅读(105)

来源: .net 温故知新:【9】.NET日志记录 ILogger使用和原理 – XSpringSun – 博客园

日志

日志作为我们程序记录的“黑匣子”不论什么系统都应该使用到的,比如我们经常使用的log4net就是第三方日志记录提供程序。.NET 支持使用各种内置和第三方日志记录提供程序的日志记录 API,这篇文章主要介绍的是内置提供程序和API的使用并看下他们是如何实现的。

日志优先级

如果你使用过log4net的话那么你对这个优先级应该不陌生,在日志记录过程中我们可以对记录的日志信息进行优先级划分,根据优先级我们可以配置只记录哪些优先级别的日志,同时日志信息也会标记这条信息的优先级。在我们查找问题的时候更好的筛选和定位。

.net 的日志优先级LogLevel 分为:

Trace = 0、Debug = 1、Information = 2、Warning = 3、Error = 4、Critical = 5 和 None = 6。

日志级别:

Trace<Debug<Information<Warning<Error<Critical<None

使用控制台输出日志

现在我们来感受下如何用内置提供程序记录日志,使用的是.NET 6 控制台程序进行示例。

  • 添加Install-Package Microsoft.Extensions.Logging 日志基础包
    Install-Package Microsoft.Extensions.Logging
  • 添加Microsoft.Extensions.Logging.Console 控制台输出日志提供程序包
    Install-Package Microsoft.Extensions.Logging.Console
  • DI注入
ServiceCollection services = new ServiceCollection();
//添加日志到容器
services.AddLogging(loggingBuilder =>
    {
        loggingBuilder.AddConsole();
    }
    //回调,或者是叫委托方法,调用AddConsole()添加控制台输入提供程序Provider。
    //AddConsole方法就是在Microsoft.Extensions.Logging.Console包中LoggingBuilder的扩展方法。
);

可以通过容器Provider直接获取日志对象然后调用写日志方法。

image

当然更常用的是在其它类中通过构造函数注入,使用 DI 中的 ILogger 对象(TCategoryName 类别字符串是任意的,但约定将使用类名称,在日志中能知道是哪个类输出的)。

    public class TestLog
    {
        private readonly ILogger _logger;
        public TestLog(ILogger<TestLog> logger)
        {
            _logger = logger;
        }

        public void Test()
        {
            _logger.LogDebug("测试");
        }
    }

上面的控制台打印我们注意到没有输出“调试日志”,“信息日志”,这是因为未设置默认日志级别,则默认的日志级别值为 Information。所以输出程序只输出>=Information的日志。

通过代码设置:
image

日志设计的原理

如下我大概画了一个逻辑图,对于如何实现日志进行了一个梳理,代码部分未.net源码截取。

image

通过如上的流程我们知道其实日志对象是由LoggerFactory类创建的,所以我们不使用注入的方式也可以直接获取日志对象并写日志。

            var loggerFactory = LoggerFactory.Create(builder =>
            {
                builder.AddConsole();
                builder.SetMinimumLevel(LogLevel.Debug);
            });

            ILogger loger = loggerFactory.CreateLogger<Program>();

image

提供程序

目前内置的日志记录提供程序:

  • Console:Console 提供程序将输出记录到控制台。
  • Debug:Debug 提供程序使用 System.Diagnostics.Debug 类写入日志输出。
  • EventSource:EventSource 提供程序写入名称为 Microsoft-Extensions-Logging 的跨平台事件源。
  • EventLog:EventLog 提供程序将日志输出发送到 Windows 事件日志。
    比如我们在测试里面添加一个EventLog将日志写入Windows 事件日志:
    安装提供程序包:Install-Package Microsoft.Extensions.Logging.EventLog
    image

内置程序未提供对日志记录到文件,所以我们可以使用一些三方包,当然也可以自己开发。

image
查看地址:三方包

在上一篇 .NET 配置 中我们也见到提供程序这个概念,或者是说这种设计结构,其实.net中很多地方都用到提供程序的思维,然可以灵活扩展。

.net 温故知新:【8】.NET 中的配置从xml转向json - XSpringSun - 博客园

mikel阅读(119)

来源: .net 温故知新:【8】.NET 中的配置从xml转向json – XSpringSun – 博客园

一、配置概述

在.net framework平台中我们常见的也是最熟悉的就是.config文件作为配置,控制台桌面程序是App.config,Web就是web.config,里面的配置格式为xml格式。
image

在xml里面有系统生成的配置项,也有我们自己添加的一些配置,最常用的就是appSettings节点,用来配置数据库连接和参数。
使用的话就引用包System.Configuration.ConfigurationManager 之后取里面的配置信息:System.Configuration.ConfigurationManager.AppSettings["ConnectionString"]

随着技术的发展这种配置方式显得冗余复杂,如果配置项太多层级关系参数表达凌乱,在.net core开始也将配置的格式默认成了json格式,包括现在很多的其它配置也是支持的,比如java中常用的yaml格式,为什么能支持这么多读取源和格式,其实质在于配置提供程序
目前.NET 中的配置是使用一个或多个配置提供程序执行的。 配置提供程序使用各种配置源从键值对读取配置数据,这些配置程序稍后我们会看到,读取的配置源可以是如下这些:

  • 设置文件,appsettings.json
  • 环境变量
  • Azure Key Vault
  • Azure 应用配置
  • 命令行参数
  • 已安装或已创建的自定义提供程序
  • 目录文件
  • 内存中的 .NET 对象
  • 第三方提供程序

二、配置初识

IConfiguration 接口是所有配置源的单个表示形式,给定一个或多个配置源,IConfiguration 类型提供配置数据的统一视图。
image

上图我们可能没有直观的感受,现在写一个例子来看看

(1). 新建控制台应用程序:
创建控制台使用的是.net 6.0 框架,vs 2022。
安装 Microsoft.Extensions.Configuration.Json NuGet 包,该包提供json配置文件读取。

Install-Package Microsoft.Extensions.Configuration.Json

image

(2). 添加appsettings.json 文件

{
  "person": {
    "name": "XSpringSun",
    "age": 18
  }
}

(3). 使用json提供程序读取json配置
new一个ConfigurationBuilder,添加json配置,AddJsonFile是在包中的IConfigurationBuilder扩展方法,其它配置提供程序也是用这种扩展方法实现。

        static void Main(string[] args)
        {

            IConfiguration configuration = new ConfigurationBuilder()
                .AddJsonFile("appsettings.json")
                .Build();

            Console.WriteLine(configuration["person:name"]);
            Console.WriteLine(configuration["person:age"]);

            Console.WriteLine("Hello, World!");
            Console.ReadLine();
        }

image

可以看到已经取到json配置文件中的值了,配置值可以包含分层数据。 分层对象使用配置键中的 : 分隔符表示。在下面的调试对象中我们可以看到实际configuration的Providers 提供程序数组有一个值,就是我们的JsonConfigurationProvider,并且JsonConfigurationProvider里面已经读取了json的数据存储在Data数组中。

对于如上几行代码干了什么呢:

  • 将 ConfigurationBuilder 实例化(new ConfigurationBuilder)。
  • 添加 “appsettings.json” 文件,由 JSON 配置提供程序识别(AddJsonFile(“appsettings.json”))。
  • 使用 configuration 实例获取所需的配置

三、选项模式

这样已经实现json进行配置读取,但是取值的方式似乎和以前没什么太大变法,所以.net提供了选项模式,选项模式就是使用类来提供对相关设置组的强类型访问。
我们创建一个Config类用来转换json:

namespace ConfigDemo
{
    public class Config
    {
        public Person? person { get; set; }
    }

    public class Person {
        public string? name { get; set; }
        public int age { get; set; }
    }
}

绑定配置

IConfiguration configuration = new ConfigurationBuilder()
                .AddJsonFile("appsettings.json")
                .Build();


            Config options = new Config();
            ConfigurationBinder.Bind(configuration, options);

            Person person = configuration.GetSection("person").Get<Person>();

            Console.WriteLine(options.person.name);
            Console.WriteLine(options.person.age);

            Console.WriteLine("-----------GetSection获取-------------");
            Console.WriteLine(person.name);
            Console.WriteLine(person.age);

image

用了两种方式获取配置,第一种使用ConfigurationBinder.Bind()将整个配置绑定到对象Config上,另外一种是使用IConfiguration的GetSection().Get<T>()并返回指定的类型。两种方式都可以使用,看实际需求和用途。

四、选项依赖注入

在控制台程序中我们引用DI注入包,然后演示下如何进行配置的注入。关于DI和IOC不清楚的看我上篇文章.net 温故知新:【7】IOC控制反转,DI依赖注入

  • 新建一个测试类TestOptionDI
    public class TestOptionDI
    {
        private readonly IOptionsSnapshot<Config> _options;
        public TestOptionDI(IOptionsSnapshot<Config> options)
        {
            _options = options;
        }

        public void Test()
        {
            Console.WriteLine("DI测试输出:");
            Console.WriteLine($"姓名:{_options.Value.person.name}");
            Console.WriteLine($"年龄:{_options.Value.person.age}");
        }
    }

在测试类中我们使用IOptionsSnapshot<T>接口作为依赖注入,还有其它不同定义的接口用来配置注入,关于选项接口:。

image

不同接口可以配合读取配置的不同方式起作用,IOptionsSnapshot接口可以在配置文件改变后不同作用域进行刷新配置。接着我们修改main方法,引入DI,并将AddJsonFile方法的参数reloadOnChange设置为true,optional参数是否验证文件存在,建议开发时都设置为true,这样如果文件有问题会进行报错。
注入配置这句services.AddOptions().Configure<Config>(e=>configuration.Bind(e))是关键,通过容器调用AddOptions方法注册,然后Configure方法里面是一个委托方法,该委托的作用就是将配置的信息绑定到Config类型的参数e上。注册到容器的泛型选项接口,这样在TestOptionDI类构造函数注入就能注入IOptionsSnapshot了,这里有点绕。

        static void Main(string[] args)
        {

            IConfiguration configuration = new ConfigurationBuilder()
                .AddJsonFile("appsettings.json",optional:true,reloadOnChange:true)
                .Build();
            
            //IServiceCollection 服务
            ServiceCollection services = new ServiceCollection();
            //注入配置
            services.AddOptions().Configure<Config>(e=>configuration.Bind(e));
            //注入TestOptionDI
            services.AddScoped<TestOptionDI>();

            using (var provider = services.BuildServiceProvider())
            {
                //获取服务
                var testOption = provider.GetRequiredService<TestOptionDI>();
                testOption.Test();
            }
            Console.ReadLine();
        }

image

为了测试IOptionsSnapshot接口在不同作用域会刷新配置,我们修改下main方法,用一个while循环在ReadLine时修改json文件值,不同的Scope里进行打印。

            using (var provider = services.BuildServiceProvider())
            {
                while (true)
                {
                    using (var scope = provider.CreateScope())
                    {
                        //获取服务
                        var testOption = scope.ServiceProvider.GetRequiredService<TestOptionDI>();
                        testOption.Test();
                    }
                    Console.ReadLine();
                }
            }

image

这个功能在web中使用很方便,因为框架的一次请求就是一个作用域,所以我们修改了配置,下次请求就能生效了,而不用重启服务。

五、其它配置

如最开始所说,不仅能配置json文件,由于各种提供程序,还可以配置其它的,但是根据配置的顺序会进行覆盖。我们只添加一个环境变量配置演示下:
首先添加提供程序包:Install-Package Microsoft.Extensions.Configuration.EnvironmentVariables
然后添加环境变量配置代码AddEnvironmentVariables()

IConfiguration configuration = new ConfigurationBuilder()
                .AddJsonFile("appsettings.json",optional:true,reloadOnChange:true)
                .AddEnvironmentVariables()
                .Build();

在VS中配置临时环境变量
image

这里有个扁平化配置,就是表示层级用冒号person:age
image

六、托管模式

对于web项目我们没有进行这么多操作它是怎么配置的呢,其实框架已经自动帮我们做了,其它非web项目也可以使用这种托管模式,在Microsoft.Extensions.Hosting 包中,只需要使用简单的代码就能配置好。

IHost host = Host.CreateDefaultBuilder(args).Build();
await host.RunAsync();

其加载配置的优先级:
image
通过分析我们对整个配置如何运行的机制有了一个大体的了解,如果想详细了解托管模式的还是建议看官方文档:.NET配置

.net 温故知新:【7】IOC控制反转,DI依赖注入 - XSpringSun - 博客园

mikel阅读(107)

来源: .net 温故知新:【7】IOC控制反转,DI依赖注入 – XSpringSun – 博客园

IOC控制反转

大部分应用程序都是这样编写的:编译时依赖关系顺着运行时执行的方向流动,从而生成一个直接依赖项关系图。 也就是说,如果类 A 调用类 B 的方法,类 B 调用 C 类的方法,则在编译时,类 A 将取决于类 B,而 B 类又取决于类 C

image

应用程序中的依赖关系方向应该是抽象的方向,而不是实现详细信息的方向。
而这就是控制反转的思想。
应用依赖关系反转原则后,A 可以调用 B 实现的抽象上的方法,让 A 可以在运行时调用 B,而 B 又在编译时依赖于 A 控制的接口(因此,典型的编译时依赖项发生反转)。 运行时,程序执行的流程保持不变,但接口引入意味着可以轻松插入这些接口的不同实现。

image

上下不同的实现方式在于之前的依赖关系是A->B->C,控制反转后A->B接口->C接口,然后具体的B,C实现又是B->B接口 的反转依赖。这样的好处就是A只依赖B接口而不是依赖实现,具体我们要实现什么只需要按照业务需求进行编写,并且可以随时替换实现而不会影响A的实现,这种思想就是控制反转。

如下是顺序依赖:

        public class A
        {
            //依赖具体类
            public B b;
            public C c;
            public A(B _b, C _c) {
                b = _b;
                c = _c;
            }
            public void Listen()
            {
                b.SayHi();
                c.SayBye();
            }
        }

        public class B
        {
            public void SayHi()
            {
                Console.WriteLine("hi...");
            }
        }
        public class C
        {
            public void SayBye()
            {
                Console.WriteLine("bye...");
            }
        }

如下是控制反转:

        public class A
        {
            //依赖接口
            public IB b;
            public IC c;
            public A(IB _b, IC _c)
            {
                b = _b;
                c = _c;
            }
            public void Listen()
            {
                b.SayHi();
                c.SayBye();
            }
        }

        public interface IB
        {
            public void SayHi();
        }
        public interface IC
        {
            public void SayBye();
        }

DI依赖注入

.NET 支持依赖关系注入 (DI) 软件设计模式,这是一种在类及其依赖项之间实现控制反转 (IoC) 的技术。
我们首先用代码来看什么是DI,在.net提供的扩展包Microsoft.Extensions.DependencyInjection中来完成DI,nuget安装。

image

然后我们实现接口B和接口C,实现我们可以说英语,也可以说汉语,我们在SayHi和SayBye中输出汉语。

        public class B : IB
        {
            public void SayHi()
            {
                Console.WriteLine("你好...");
            }
        }

        public class C : IC
        {
            public void SayBye()
            {
                Console.WriteLine("再见...");
            }
        }

然后在服务容器中注册依赖关系。 .NET 提供了一个内置的服务容器 IServiceProvider。 服务通常在应用启动时注册,并追加到 IServiceCollection。 添加所有服务后,可以使用 BuildServiceProvider 创建服务容器,然后在容器中直接“要”对象而不用去管它如何实例化,并且DI具备传染性,假如B引用了D接口ID,那么我们注册B并在获取B实例时,引用的D接口也会被实例化。

            //IServiceCollection 服务
            IServiceCollection services = new ServiceCollection();
            //服务注册
            services.AddTransient<A>();
            services.AddTransient<IB, B>();
            services.AddTransient<IC, C>();
            //创建服务容器
            var serviceProvider = services.BuildServiceProvider();
            //获取服务
            var a = serviceProvider.GetRequiredService<A>();
            //使用
            a.Listen();
            Console.ReadKey();

image

这就是通过DI依赖注入的方式来实现IOC的思想,或许你会好奇为什么我们不直接实例化A,然后在构造方法里面传进去就行了,也就不依赖DI实现了。但是程序结构更复杂些呢,比如上面提到的B又有D,D又有F呢,这样在构造的时候不是一直要new很多对象,而且同一个接口的不同实现还要去找实例化处的代码进行修改。例如SayHI我想说英文呢?那么我们就可以实现一个BB,然后在服务注册的地方注册BB就可以了。

        public class BB : IB
        {
            public void SayHi()
            {
                Console.WriteLine("hello...");
            }
        }

替换注册BB services.AddTransient<IB, BB>(),而不用去改任何逻辑。
image

服务生命周期

在注册服务的时候我使用的AddTransient方法,表示注册的服务是瞬态的,也就是每次请求都是重新创建实例。同时还提供其它注册服务的方法。
image
服务有三种声明周期:

  • 瞬态
  • 作用域
  • 单例
  1. 瞬态
    服务是每次从服务容器进行请求时创建的。 这种生存期适合轻量级、 无状态的服务。 用 AddTransient 注册服务。在处理请求的应用中,在请求结束时会释放暂时服务。
  2. 作用域
    指定了作用域的生存期指明了每个客户端请求(连接)创建一次服务。 向 AddScoped 注册范围内服务。在处理请求的应用中,在请求结束时会释放有作用域的服务。
    ASP.NET 在处理一个请求的时候是一个作用域,同样我们自己也可以定义作用域。使用serviceProvider.CreateScope()创建作用域,在作用域释放后对象将被释放。
    image
    我们使用AddScoped添加对象,然后在作用域中取两个A对象进行比较,可以看到是True
    如果我们用AddTransient注册A,即使在作用域内两个对象比较也是不一样的,结果为False
    image
  3. 单例
    单例大家应该好理解,就是设计模式中的单例,使用AddSingleton 注册,在首次请求它们时进行创建;或者在向容器直接提供实现实例时由开发人员进行创建。 很少用到此方法,因为可能是线程不安全的,如果服务中有状态。

其它

在Microsoft.Extensions.DependencyInjection中只能用构造函数注入,其它框架还提供属性注入,比如autofac。至于原因不得而知,当然也看个人喜好。查了些资料说是构造函数注入更科学,在对象创建的瞬间对象的构造方法将服务实例化,避免逻辑问题。

.net 温故知新:【6】Linq是什么 - XSpringSun - 博客园

mikel阅读(96)

来源: .net 温故知新:【6】Linq是什么 – XSpringSun – 博客园

1、什么是Linq

关于什么是Linq 我们先看看这段代码。

            List<int> list = new List<int> { 1, 1, 2, 2, 3, 3, 3, 5, 7, 8, 10, 12 };
            var linqList = list.Where(t => t < 10)              //列表中值小于10
                           .GroupBy(t => t)                     //分组
                           .Where(t => t.Count() > 1)           //分组后出现次数大于1
                           .OrderByDescending(t => t.Count())   //按照出现次数倒序
                           .Select(t => t.Key);                 //选择值
            Console.WriteLine(string.Join(' ',linqList));


这段代码使用Linq对List列表进行筛选、分组、排序等一系列操作展示了Linq的强大和便捷,那么我们为什么需要学习Linq?可以看到这样一堆逻辑只几行Linq很快就可以实现,如果要我们自己实现方法去处理这个List肯定是比较繁琐的。
Linq是什么?如下是官方文档对于Linq的描述:

语言集成查询 (LINQ) 是一系列直接将查询功能集成到 C# 语言的技术统称。 数据查询历来都表示为简单的字符串,没有编译时类型检查或 IntelliSense 支持。 此外,需要针对每种类型的数据源了解不同的查询语言:SQL 数据库、XML 文档、各种 Web 服务等。 借助 LINQ,查询成为了最高级的语言构造,就像类、方法和事件一样。
对于编写查询的开发者来说,LINQ 最明显的“语言集成”部分就是查询表达式。 查询表达式采用声明性查询语法编写而成。 使用查询语法,可以用最少的代码对数据源执行筛选、排序和分组操作。 可使用相同的基本查询表达式模式来查询和转换 SQL 数据库、ADO .NET 数据集、XML 文档和流以及 .NET 集合中的数据。

Linq的使用频率和范围可以说是很高很广的,基本每天应该都会用到,那么Linq到底是什么呢?怎么实现的?
要学习Linq首先需要先了解委托Lambda 表达式,因为Linq是由 委托->Lambda->Linq 的一个变换过程。

2、委托

委托简单来讲就是指向方法的指针,就像变量是用来指向具体实现。例如String对象,我们定义一个对象string str="变量"那么str就是指向具体实例化对象的地址,String就是类型。
按照这个思路,如果我们要定义一个指向方法的变量,委托就是为了实现该目的。委托使用 delegate 关键字来声明委托类型。
用类似于定义方法签名的语法来定义委托类型。 只需向定义添加 delegate 关键字即可,如下我们定义一个比较两个数字的委托类型。

//比较两个数字
public delegate int Comparison(int i, int n);

接着我们定义委托变量comparison并指向方法ComparisonMax方法,该方法比较两个int大小,返回大的一个。

委托是和类平级的应以,理应放类同级别,但是C#支持类嵌套定义,所以我们把和本类关联性强的委托可以嵌套定义,委托变量comparison指向方法后,调用comparison(1, 2)执行委托方法并打印。
当然委托可以有返回值也可以定义void无返回值,关于委托的其它方面这里不再赘述,这里主要是为了看清Linq所以浅显的梳理下。
每次使用委托的时候我们都要定义比较麻烦,所以框架已经为我们定义好了两个类型,ActionFunc一个无返回值,一个有返回值,并且采用泛型定义了多个委托以满足我们日常使用。

有了这两个系列的委托类型,上面的方式我们也可以不定义委托直接使用Func<int,int,int> comparison = ComparisonMax;来实现。

3、Lambda

在看Lamda之前我们再看下委托方法的另外一种编写方式,匿名方法

delegate 运算符创建一个可以转换为委托类型的匿名方法
如下我们直接在委托变量后面使用delegate 将参数方法体直接写,而不用声明其名称的方式。


Func<int,int,int> comparison = delegate(int i,int n) { return i > n ? i : n; };
         

运行打印下结果:

从 C# 3 开始,lambda 表达式提供了一种更简洁和富有表现力的方式来创建匿名函数。 使用 => 运算符构造 Lambda
在 lambda 表达式中,lambda 运算符 将左侧的输入参数与右侧的 lambda 主体分开。

使用 Lambda 表达式来创建匿名函数。 使用 lambda 声明运算符=>(读作 goes to) 从其主体中分离 lambda 参数列表。 Lambda 表达式可采用以下任意一种形式:

其中第一种后面写表达式,第二种是使用大括号{}的代码块作为主体,语句 lambda 与表达式 lambda 类似,只是语句括在大括号中。
其实 表达式lambda 就是 语句lambda 在只有一行的情况下可以省略大括号和return。表达式 lambda 的主体可以包含方法调用。 不过若在表达式树中,则不得在 Lambda 表达式中使用方法调用。表达式树是另外一个东西,我们现在使用的ORM框架就是将lambda转换为sql,这个过程使用表达式树技术,比如EF查询中,如果我们写一个Console.WriteLine()表达式树是没办法转换的,想一下这个调用对于sql查询来说是没有意义的,表达式树以后再讨论吧。

因此上面的匿名函数可以通过lambda变换为:


Func<int,int,int> comparison = (int i,int n) =>{ return i > n ? i : n; };
         

Lambda表达式参数类型也可以省略,输入参数类型必须全部为显式或全部为隐式;否则,便会生成 CS0748 编译器错误。
所以表达式还可以变换为:


Func<int,int,int> comparison = (i,n) =>{ return i > n ? i : n; };
         

将 lambda 表达式的输入参数括在括号中。 如果没有参数则直接写():Action ac = () => {Console.WriteLine();}或者Action ac = () => Console.WriteLine()
如果 lambda 表达式只有一个输入参数,则括号是可选:Func<int,int> fun = i => {return i++;}或者Func<int,int> fun = i =>i++
关于更多的lambda知识可以参看文档:Lambda 表达式

4、实现一个Linq

有了委托和Lambda 的知识,我们可以自己写一个简易的Linq实现,写一个where吧。
我们需要扩展List类的方法,当然不用扩展方法也是可以实现,直接写方法然后调用,但是为了还原框架实现方式,我们模仿IEnumerable类(List 继承至IEnumerable)。

关于扩展方法:

扩展方法使你能够向现有类型“添加”方法,而无需创建新的派生类型、重新编译或以其他方式修改原始类型。 扩展方法是一种静态方法,但可以像扩展类型上的实例方法一样进行调用。
扩展方法被定义为静态方法,但它们是通过实例方法语法进行调用的。 它们的第一个参数指定方法操作的类型。 参数前面是 this 修饰符。 仅当你使用 using 指令将命名空间显式导入到源代码中之后,扩展方法才位于范围中。

  • 定义扩展方法
    public static class MyLinq
    {
        public static List<T> MyLinqWhere<T>(this List<T> list, Func<T, bool> predicate)
        {
            List<T> tempList = new List<T>();
            foreach (var item in list)
            {
                if (predicate(item))
                {
                    tempList.Add(item);
                }
            }
            return tempList;
        }
    }

List类是泛型,所以我们定义泛型MyLinqWhere 方法,第一个参数使用this关键字修饰,然后predicate为一个输入参数是T返回时bool的委托用来进行对List里面的每一个元素进行筛选,返回的bool结果判断是否符合要求。
我们将符合要求的元素放到一个新的List里面最后返回该List。

  • 使用Linq方式调用自定义的where方法
     List<int> list = new List<int> { 1, 1, 2, 2, 3, 3, 3, 5, 7, 8, 10, 12 };
      var listWhere = list.MyLinqWhere(x => x < 7);
      Console.WriteLine(string.Join(' ', listWhere));

这样就实现了一个简单的Linq,虽然实际的IEnumerable扩展方法里面还有其它操作,但是通过这个过程我们知道了Linq的实现。
在IEnumerable扩展方法返回参数仍然是IEnumerable,所以可以像开始我们写的那样进行链式调用

5 Linq的另外一种写法

在刚开始的例子中我们换另外一种写法:

var linqList2 = from t in list
                   where t < 10
                   group t by t into t
                   where t.Count() > 1
                   orderby t.Count() descending
                   select t.Key;

输出的结果和方法调用,使用Lambda出来的结果是一样的。

这种方式称为语言集成查询,查询表达式采用声明性查询语法编写而成。 使用查询语法,可以用最少的代码对数据源执行筛选、排序和分组操作。 可使用相同的基本查询表达式模式来查询和转换 SQL 数据库、ADO .NET 数据集、XML 文档和流以及 .NET 集合中的数据。
这种写法只是一种语法方式,或者说语法糖,在编译阶段生成的代码和Lambda表达式生成的代码是一致的,虽然这种方法看起来比较炫酷,但是目前大家还是比较习惯Lambda的书写方式和阅读,了解就行了,要详细学习可以参看官方文档。

.net 温故知新:【5】异步编程 async await - XSpringSun - 博客园

mikel阅读(84)

来源: .net 温故知新:【5】异步编程 async await – XSpringSun – 博客园

1、异步编程

异步编程是一项关键技术,可以直接处理多个核心上的阻塞 I/O 和并发操作。 通过 C#、Visual Basic 和 F# 中易于使用的语言级异步编程模型,.NET 可为应用和服务提供使其变得可响应且富有弹性。

上面是关于异步编程的解释,我们日常编程过程或多或少的会使用到异步编程,为什么要试用异步编程?因为用程序处理过程中使用文件和网络 I/O,比如处理文件的读取写入磁盘,网络请求接口API,默认情况下 I/O API 一般会阻塞。
这样的结果是导致我们的用户界面卡住体验差,有些服务器的硬件利用率低,服务处理能力请求响应慢等问题。基于任务的异步 API 和语言级异步编程模型改变了这种模型,只需了解几个新概念就可默认进行异步执行。

现在普遍使用的异步编程模式是TAP模式,也就是C# 提供的 async 和 await 关键词,实际上我们还有另外两种异步模式:基于事件的异步模式 (EAP),以及异步编程模型 (APM) 。

APM 是基于 IAsyncResult 接口提供的异步编程,例如像FileStream类的BeginRead,EndRead就是APM实现方式,提供一对开始结束方法用来启动和接受异步结果。使用委托的BeginInvoke和EndInvoke的方式来实现异步编程。
EAP 是在 .NET Framework 2.0 中引入的,比较多的体现在WinForm编程中,WinForm编程中很多控件处理事件都是基于事件模型,经常用到跨线程更新界面的时候就会使用到BeginInvoke和Invoke。事件模式算是对APM的一种补充,定义了一系列事件包括完成、进度、取消的事件让我们在异步调用的时候能注册响应的事件进行操作。

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(DateTime.Now + " start");
        IAsyncResult result = BeginAPM();
        //EndAPM(result);
        Console.WriteLine(DateTime.Now + " end");

        Console.ReadKey();
    }


    delegate void DelegateAPM();
    static DelegateAPM delegateAPM = new DelegateAPM(DelegateAPMFun);

    public static IAsyncResult BeginAPM()
    {
        return delegateAPM.BeginInvoke(null, null);
    }

    public static void EndAPM(IAsyncResult result)
    {
        delegateAPM.EndInvoke(result);
    }
    public static void DelegateAPMFun()
    {
        Console.WriteLine("DelegateAPMFun...start");
        Thread.Sleep(5000);
        Console.WriteLine("DelegateAPMFun...end");

    }
}

如上代码我使用委托实现异步调用,BeginAPM 方法使用 BeginInvoke 开始异步调用,然后 DelegateAPMFun 异步方法里面停5秒。看下下面的打印结果,是 main 方法里面的打印在前,异步方法里面的打印在后,说明该操作是异步的。

其中一行代码EndAPM(result)被注释了,调用了委托 EndInvoke 方法,该方法会阻塞程序直到异步调用完成,所以我们可以放到适当的位置用来获取执行结果,这类似于TAP模式的await 关键字,放开改行代码执行下。

以上两种方式已不推荐使用,编写理解起来比较晦涩,感兴趣的可以自行了解下,而且这种方式在.net 5里面已经不支持委托的异步调用了,所以如果要运行需要在.net framework框架下。
TAP 是在 .NET Framework 4 中引入的,是目前推荐的异步设计模式,也是我们本文讨论的重点方向,但是TAP并不一定是线程,他是一种任务,理解为工作的异步抽象,而非在线程之上的抽象。

2、async await

使用 async await 关键字可以很轻松的实现异步编程,我们子需要将方法加上 async 关键字,方法内的异步操作使用 await 等待异步操作完成后再执行后续操作。

class Program
{

    static void Main(string[] args)
    {
        Console.WriteLine(DateTime.Now + " start");
        AsyncAwaitTest();
        Console.WriteLine(DateTime.Now + " end");
        Console.ReadKey();
    }

    public static async void AsyncAwaitTest()
    {
        Console.WriteLine("test start");
        await Task.Delay(5000);
        Console.WriteLine("test end");
    }
}

AsyncAwaitTest 方法使用 async 关键字,使用await关键字等待5秒后打印”test end”。在 Main 方法里面调用 AsyncAwaitTest 方法。

使用 await 在任务完成前将控制让步于其调用方,可让应用程序和服务执行有用工作。 任务完成后代码无需依靠回调或事件便可继续执行。 语言和任务 API 集成会为你完成此操作。
使用await 的方法必须使用 async 关键字,如果我们 Main 方法里面想等待 AsyncAwaitTest 则 Main 方法需要加上 async 并返回 Task。

3、async await 原理

将上面 Main 方法不使用 await 调用的方式编译后使用ILSpy反编译dll,使用C# 4.0才能看到编译器为我们做了什么。因为4.0不支持 async await 所以会反编译到具体代码,4.0 以后的反编译后会直接显示 async await 语法。

通过反编译后可以看到在异步方法里面重新生成了一个泛型类 d__1 实现接口IAsyncStateMachine,然后调用Start方法,Start中进行了一些线程处理后调用 stateMachine.MoveNext() 即调用d__1实例化对象的MoveNext方法。

public static void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine
{
	if (stateMachine == null)
	{
		ThrowHelper.ThrowArgumentNullException(ExceptionArgument.stateMachine);
	}
	Thread currentThread = Thread.CurrentThread;
	Thread thread = currentThread;
	ExecutionContext executionContext = currentThread._executionContext;
	ExecutionContext executionContext2 = executionContext;
	SynchronizationContext synchronizationContext = currentThread._synchronizationContext;
	try
	{
		stateMachine.MoveNext();
	}
	finally
	{
		SynchronizationContext synchronizationContext2 = synchronizationContext;
		Thread thread2 = thread;
		if (synchronizationContext2 != thread2._synchronizationContext)
		{
			thread2._synchronizationContext = synchronizationContext2;
		}
		ExecutionContext executionContext3 = executionContext2;
		ExecutionContext executionContext4 = thread2._executionContext;
		if (executionContext3 != executionContext4)
		{
			ExecutionContext.RestoreChangedContextToThread(thread2, executionContext3, executionContext4);
		}
	}
}

我们再看编译器为生成的类 <AsyncAwaitTest>d__1 :

MoveNext方法将 AsyncAwaitTest 逻辑代码包含进去了,我们的源代码因为只有一个 await 操作,如果有多个 await 操作,那么MoveNext里面应该还会有多个分段逻辑,将不同段的MoveNext放入不同的状态分段块。
在该类中也有一个if判断,按照 1__state 状态参数,最开始调用的时候是-1,执行进来 num != 0 则执行我们的业务代码if里面的,这个时候会顺序执行业务代码,直到碰到 await 则执行如下代码

awaiter = Task.Delay(5000).GetAwaiter();
if (!awaiter.IsCompleted)
{
    num = (<> 1__state = 0);

    <> u__1 = awaiter;

    < AsyncAwaitTest > d__1 stateMachine = this;

    <> t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
    return;
}

在该过程中 <> t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine) 将 await 句和状态机进行传递调用 AwaitUnsafeOnCompleted方法,该方法一直跟下去会找到线程池的操作。

// System.Threading.ThreadPool
internal static void UnsafeQueueUserWorkItemInternal(object callBack, bool preferLocal)
{
    s_workQueue.Enqueue(callBack, !preferLocal);
}

程序将封装的任务放入线程池进行调用,这个时候异步方法就切换到了另一个线程,或者在原线程上执行(如果异步方法执行时间比较短可能就不会进行线程切换,这个主要看调度程序)。
执行完成 await 后状态 1__state 已经更改了为 0,程序会再次调用 MoveNext 进入 else 之后没有return和其它逻辑,则继续执行到结束。
可以看到这是一个状态控制的执行逻辑,是一种“状态机模式”的设计模式,对于 Main 方法调用 AsyncAwaitTest 逻辑此刻进入if,碰到await则进入线程调度执行,如果异步方法切换到其它线程调用,则方法 Main 继续执行,当状态机执行切换到另外一个状态后再次 MoveNext 直到执行完异步方法。

4、async 与 线程

有了上面的基础我们知道 async 与 await 通常是成对配合使用的,当我们的方法标记为异步的时候,里面的耗时操作就需要 await 进行标记等待完成后执行后续逻辑,调用该异步方法的调用者可以决定是否等待,如果不用 await 则调用者异步执行或者就在原线程上执行异步方法。

如果 async 关键字修改的方法不包含 await 表达式或语句,则该方法将同步执行,可选择性通过 Task.Run API 显式请求任务在独立线程上运行。
可以将 AsyncAwaitTest 方法改为显示线程运行:

public static async Task AsyncAwaitTest()
{
    Console.WriteLine("test start");
    await Task.Run(() =>
    {
        Thread.Sleep(5000);
    });
    Console.WriteLine("test end");
}

5、取消任务 CancellationToken

如果不想等待异步方法完成,可以通过 CancellationToken 取消该任务,CancellationToken 是一个struct,通常使用 CancellationTokenSource 来创建 CancellationToken,因为CancellationTokenSource 有一些列的[方法]用于我们取消任务而不用去操作CancellationToken 结构体。

CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken ct = cts.Token;

然我改造下方法,将 CancellationToken 传递到异步方法,cts.CancelAfter(3000) 3秒钟后取消任务,我们监听CancellationToken 如果 IsCancellationRequested==true 则直接返回 。

static void Main(string[] args)
{
    CancellationTokenSource cts = new CancellationTokenSource();
    CancellationToken ct = cts.Token;
    cts.CancelAfter(3000);

    Console.WriteLine(DateTime.Now + " start");
    AsyncAwaitTest(ct);
    Console.WriteLine(DateTime.Now + " end");
    Console.ReadKey();
}

public static async Task AsyncAwaitTest(CancellationToken ct)
{
    Console.WriteLine("test start");
    await Task.Delay(5000);
    Console.WriteLine(DateTime.Now + " cancel");
    if (ct.IsCancellationRequested) {
        return;
    }
    //ct.ThrowIfCancellationRequested();
    Console.WriteLine("test end");
}

因为我们是手动通过代码判断状态结束异步,所以即使在3秒后就已经结束了任务,但是await Task.Delay(5000) 任然会等待5秒执行完。还有一种方式就是我们不判断是否取消,直接调用ct.ThrowIfCancellationRequested() 给我们判断,这个方法如果,但是任然不能及时结束。这个时候我们还有另外一种处理方式,就是将CancellationToken 传递到 await 的异步API方法里,可能会立即结束,也可能不会,这个要取决异步实现。

public static async Task AsyncAwaitTest(CancellationToken ct)
{
    Console.WriteLine("test start");
    //传递CancellationToken 取消
    await Task.Delay(5000,ct);
    Console.WriteLine(DateTime.Now + " cancel");
    
    //手动处理取消
    //if (ct.IsCancellationRequested) {
    //    return;
    //}

    //调用方法处理取消
    //ct.ThrowIfCancellationRequested();
    Console.WriteLine("test end");
}

6、注意项

在异步方法里面不要使用 Thread.Sleep 方法,有两种可能:
1、Sleep在 await 之前,则会直接阻塞调用方线程等待Sleep。
2、Sleep在 await 之后,但是 await 执行在调用方的线程上也会阻塞调用方线程。
所以我们应该使用 Task.Delay 用于等待操作。那为什么我上面的 Task.Run 里面使用了 Thread.Sleep呢,因为 Task.Run 是显示请求在独立线程上运行,所以我知道这里写不会阻塞调用方,上面我只是为了演示,所以不建议用。

.net 温故知新:【4】NuGet简介和使用 - XSpringSun - 博客园

mikel阅读(98)

来源: .net 温故知新:【4】NuGet简介和使用 – XSpringSun – 博客园

在包管理以前我们在项目中引用第三方包通常是去下载dll放到项目中再引用,后来逐渐发展成各种包管理工具,nuget就是一种工具,适用于任何现代开发平台的基本工具可充当一种机制,通过这种机制,开发人员可以创建、共享和使用有用的代码。 通常,此类代码捆绑到“包”中,其中包含编译的代码(如 DLL)以及在使用这些包的项目中所需的其他内容。
Linux 我们可以使用apt、yum来安装软件,js 可以使用npm来搭建下载,Java 有maven管理包,而对于.net nuget就是同样效果和机制的工具。

NuGet 客户端工具

要使用 NuGet,作为软件包使用者或创建者,可以使用命令行接口 (CLI) 工具以及 Visual Studio 中的 NuGet 功能。

CLI工具可以使用 dotnet CLI 或 nuget.exe CLI。

dotnet CLI 随某些 Visual Studio 工作负载一起安装,例如 .NET Core 。从 Visual Studio 2017 开始dotnet CLI 将自动随任何与 .NET Core 相关的工作负载一起安装。
dotnet CLI 适用于 .NET Core 和 .NET Standard 项目(SDK 样式的项目类型),以及任何其他 SDK 样式项目(例如,面向 .NET Framework 的 SDK 样式项目)
也就是说安装VS的时候会自动包含在工作负载中,对于.net 5 也可以直接安装.NET SDK,如之前.net 知新:【1】 .Net 5 基本概念文章介绍中可以看到.NET SDK是包含了CLI的。
而对于.NET Framework(仅限非 SDK 样式项目),使用 nuget.exe CLI。这种方式现在基本不使用,因为我们一般不会去单独安装,都是安装VS后直接使用就行了,除非你还在使用Visual Studio 2017 以前的版本。

至于他们有什么区别呢?

第一个是以前.NET Framework时期使用包管理的方式是使用单独的 packages.config 文件进行管理。

但是不建议使用packages.config,.NET Framework可以在VS中右键点击packages.config迁移到PackageReference。

现在.net 5的项目默认使用 PackageReference,包保留在 global-packages 文件夹中(而不是解决方案中的 packages 文件夹中)。
PackageReference 仅列出那些直接安装在项目中的 NuGet 包,不会显示引用包所包含的低级依赖更加简洁。

比如我们使用nuget安装NPOI包,它的依赖如下:

在.net framework的packages.config文件中看到NPOI和它的依赖项

在.net 5项目文件中只有NPOI

第二个就是两个工具的功能有差异

某些高级功能无法使用的时候我们就需要用命令的方式。

visual studio 使用 nuget

在VS里面有两种方式管理nuget包。第一种是右键项目->管理程序nuget包 进入导UI界面。

可以进行程序包的查找和安装的包管理,对包进行卸载更新。 在右上角有一个程序包源,可以进行包源设置,设置包源地址。默认是将 NuGet.org 用作 NuGet 客户端的包存储库。
所以我们配置应使用以下 V3 API 终结点:

https://api.nuget.org/v3/index.json

NuGet.org 是 NuGet 包的公用主机,NuGet 技术还支持在云中(如在 Azure DevOps 上)、在私有网络中或者甚至直接在本地文件系统以私密方式托管包。
https://www.nuget.org/ 打开NuGet.org站点可以进行包搜索和包的上传等。

另外一种方式就是工具->nuget管理器->程序包管理器控制台 ,调出控制台后就可以使用cli命令进行nuget包管理了。
所以有时候我们搜索文章的时候看到别人添加包,命令dotnet add package Newtonsoft.Json 我们要知道这是nuget包添加,程序包管理器控制台执行,或者在ui界面搜索包可视化操作添加,以前的 nuget.exe CLI添加包是install 命令,要注意区分下。

创建发布包

首先需要设置属性,创建包需要以下属性。

  • PackageId,包标识符,在托管包的库中必须是唯一的。 如果未指定,默认值为 AssemblyName。
  • Version,窗体 Major.Minor.Patch[-Suffix] 中特定的版本号,其中 -Suffix 标识预发布版本。 如果未指定,默认值为 1.0.0。
  • 包标题应出现在主机上(例如 nuget.org)
  • Authors,作者和所有者信息。 如果未指定,默认值为 AssemblyName。
  • Company,公司名称。 如果未指定,默认值为 AssemblyName。

在 Visual Studio 中,可以在项目属性中设置这些值(在解决方案资源管理器中右键单击项目,选择“属性” ,然后选择“包” 选项卡)。 也可以直接在项目文件 (.csproj) 中设置这些属性。

在包的 NuGet.org 页面上所示的包说明可以在 .csproj 文件中的 设置,或者通过 .nuspec 文件中的 $description 拉取。
.nuspec 文件是包含包元数据的 XML 清单,.nuspec 当你创建包时将生成。

运行 pack 命令

运行dotnet pack 命令会打包解决方案中可打包的所有项目,也可以在项目属性上设置“在构建时生成NutGet包”。

具有 .nupkg 扩展名的 NuGet 包只是一个 zip 文件。 若要轻松查看任何包的内容,只需将扩展名更改为 .zip 并按常规方法展开内容。 尝试将包上传到主机前,请务必将扩展名改回 .nupkg。
命令执行完成后打包后生成的文件路径会显示在控制台上,到目录查看到ConsoleAppNet5.1.0.0.nupkg 包,并复制一个改成zip验证。

发布到 nuget.org

登录到nuget.org,使用 Microsoft 帐户进行登录,然后选择upload上传,选择了文件后会进行自动校验,如果有问题处理后再重新上传。
还可以通过命令的方式去上传,但是需要api密钥,自己去看下官网好了。

.net 温故知新:【3】.net 5 项目结构说明和发布部署 - XSpringSun - 博客园

mikel阅读(91)

来源: .net 温故知新:【3】.net 5 项目结构说明和发布部署 – XSpringSun – 博客园

.net 5的项目目录结构和.net framework有些明显的变化,包括显示结构和项目文件,从这两个方面看看有哪些变化。

项目目录结构

就以上篇用的demo项目为例(【.net 知新:【2】 .Net Framework 、.Net 5、 .NET Standard的概念与区别】),先看看.net 5项目目录结构。

.net5 项目和原来.net framework项目最大的不同在于引用和项目文件,.net 5变成了依赖项,里面清晰的区分了包、分析器、框架、项目等内容,这样分门别类更方便我们查找和管理引用。

.net framework的所有引用都显示在一起,包括其它项目、nuget包、dll等等引用。但是他们最终都是用项目文件来管理这些东西,所以我们再看看他们项目文件的差异。

项目文件

在两个项目中都引用了nuget包 Newtonsoft.Json,添加了ClassLibraryTest项目引用,建了一个Class1.cs的类文件。
在项目里面找到项目文件.csproj,打开两个文件对比,左边是.net 5右边是.net framework 4.6.1。

相对于.net framewokr而言,.net 5项目文件会少很多内容:
在.net framework中所有引用类库都包含在项目文件中,.net 5是包含在框架中。
在.net framework中所有包含文件描述都在项目文件中,.net 没有任何项目包含文件的描述。

所以.net 5的项目文件描述信息在哪儿呢,现在我在项目中排除类文件Class1.cs。

再打开两个项目文件对比,折叠起其他项。

在.net 5中排除项用<Compile Remove="Class1.cs" />"在编译的时候移除Class1.cs。
在.net framework中因为是包含了所有的项目文件,所以排除就将<Compile Include="Class1.cs" />移除就行了。

.net 5中的这个小的改动会让我们的项目文件大大减少,试想如果我们的文件成千上万个那么.csproj的大小和阅读.net 5就会是巨大优势。
.net 5中默认是包含所有文件,如果要排除某文件直接去除包含项就行了,但是作为一个正常项目不会有太多的排除而是大量的包含,所以.net 5的改动优化相当精妙。
.net 5可以直接双击项目就能在vs中打开.csproj,.net framework需要到目录中去打开。

项目发布

可在两种模式下发布使用 .NET 创建的应用程序,模式会影响用户运行应用的方式。
将应用作为独立应用,生成的应用程序将包含 .NET 运行时和库,以及该应用程序及其依赖项。 应用程序的用户可以在未安装 .NET 运行时的计算机上运行该应用程序。
如果将应用发布为依赖于框架的应用,生成的应用程序将仅包含该应用程序本身及其依赖项。 应用程序的用户必须单独安装 .NET 运行时。
默认情况下,这两种发布模式都会生成特定于平台的可执行文件。 不使用可执行文件也可以创建依赖于框架的应用程序,这些应用程序是跨平台的。

首先我们项目右键发布,选择文件夹方式发布,然后就生成了发布配置。

点击编辑或者设置可以进行发布项配置。如上面官方文档描述,有【独立】和【依赖框架】两种方式。
如果发布【依赖框架】那么运行环境需要安装.net 运行时,并且在发布配置“目标运行时”可以选择“可移植”,因为运行时是自主安装不需要包含,所以不需要发布指定的运行时。

如果选择【独立】那么“目标运行时”只能选择特定的。因为包含了.net运行时和库,所以需要进行选择。如果不选择特定平台,这样就没办法将.net运行时和库正确的发布。

发布项目,然后到发布目录看下两种方式的文件区别

  • 依赖框架->可移植

  • 独立->win-x64(太长了截取一部分)

以上就是发布的简单介绍,采用独立的方式发布时间会久一点,另外在发布配置里面还有个“文件发布选项”,有几个配置简单说明下,有兴趣的可以对比下发布的文件区别。

  1. 生成单个文件:这个就是字面意思,通过将所有依赖应用程序的文件捆绑到一个二进制文件中,这种方式适用于将项目用作第三方库或者应用程序,方便传输管理。
  2. 启用ReadyToRun编译:可以通过将应用程序集编译为 ReadyToRun (R2R) 格式来改进 .NET Core 应用程序的启动时间和延迟。R2R 二进制文件通过减少应用程序加载时实时 (JIT) 编译器需要执行的工作量来改进启动性能。
  3. 裁剪未使用的程序集:也是字面意思,目前还是预览版,无法可靠地分析各种有问题的代码模式(主要集中在反射使用),应用程序的生成时间分析可能会导致运行时失败。这个功能最有用的应该是独立发布的方式,通过裁剪以减小部署大小。