一题多解,ASP.NET Core应用启动初始化的N种方案[上篇]

ASP.NET Core应用本质上就是一个由中间件构成的管道,承载系统将应用承载于一个托管进程中运行起来,其核心任务就是将这个管道构建起来。在ASP.NET Core的发展历史上先后出现了三种应用承载的编程方式,而且后一种编程模式都提供了针对之前编程模式的全部或者部分兼容,这就导致了一种现象:相同的功能具有N种实现方式。对这个发展历程不是特别了解的读者会有很多疑问?为什么这么多不同的编程模式都在做同一件事?它们之间的有什么差别之处?为什么有的API在最新的Minimal API又不能用了呢?[本文部分内容来源于《ASP.NET Core 6框架揭秘》第15章]

目录
一、应用承载过程中需要哪些初始化工作?
二、第一代应用承载模型
     基本编程模式
     利用环境变量和命令行参数
    承载环境设置方法
    使用Startup类型
三、第二代应用承载模型
     基本编程模式
     承载环境设置方法
     针对IWebHostBuilder的适配
    Startup构造函数注入的限制

一、应用承载过程中需要哪些初始化工作?

我们所谓的应用承载(Hosting)本就是将一个ASP.NET Core应用在一个具体的进程(Self-Host进程、IIS工作进程或者Windows Service进程等)中被启动的过程,在这个过程中需要利用提供的API完成一些必要的初始化工作。由于ASP.NET Core应用本质上就是一个由中间件构成的管道,所有整个初始化过程的目的就是为了构建这一中间件管道,毫不夸张地说,构建的中间件管道就是“应用”本身,所以“中间件注册”是最为核心的初始化工作。由于依赖注入的广泛应用,中间件的功能基本都依赖于注入的服务来完成,所以将依赖服务注册到依赖注入框架是另一项核心的初始化工作。

和任何类型的应用一样,ASP.NET Core同样需要通过配置来动态改变其运行时行为,所以针对配置的设置也是并不可少的。一个ASP.NET Core应用的配置分为两类,一种是用在中间件管道构建过程中,也就是应用承载过程中,我们将其称为“承载配置(Hosting Configuration)”。另一类配置则被用来控制中间件管道处理请求的行为,正如上面所说,中间件管道就是应用本身,所以这类配置被称为应用配置(App Configuration)。承载配置中有一个重要的组成部分,那就是描述当前的承载环境(Hosting Environment),比如应用的标识、部署环境的名称、存放内容文件和Web资源的目录等。承载配置最终会合并到应用配置中。

综上所示,ASP.NET Core应用承载的编程模型主要完成如下几种初始化工作,这些工作都具有N种实现方法。在接下来的内容中,我们将逐个介绍在三种不同的应用承载方式中,这些功能都有哪些实现方式。

  • 中间件注册

  • 服务注册

  • 承载配置的设置

  • 应用配置的设置

  • 承载环境的设置

二、第一代应用承载模型

ASP.NET Core 1.X/2.X采用的承载模型以如下图所示的IWebHostBuilder和IWebHost为核心。IWebHost对象代表承载Web应用的宿主(Host),管道随着IWebHost对象的启动被构建出来。IWebHostBuilder对象作为宿主对象的构建者,我们针对管道构建的设置都应用在它上面。

ffa8897c7c4d81bb582c4671f4765f06.png

基本编程模式

现在我们将针对上述5种初始化设置放在一个简单的演示实例中。该演示实例会注册如下这个FoobarMiddleware中间件,后者利用注入的IHandler服务完成请求的处理工作。作为IHandler接口的默认实现类型,Handler利用构造函数注入的IOptions<FoobarbazOptions>对象得到配置选项FoobarbazOptions,并将其内容作为请求的响应。

public class FoobarMiddleware
{private readonly RequestDelegate _next;public FoobarMiddleware(RequestDelegate _) { }public Task InvokeAsync(HttpContext httpContext, IHandler handler) => handler.InvokeAsync(httpContext);
}public interface IHandler
{Task InvokeAsync(HttpContext httpContext);
}public class Handler : IHandler
{private readonly FoobarbazOptions _options;private readonly IWebHostEnvironment _environment;public Handler(IOptions<FoobarbazOptions> optionsAccessor, IWebHostEnvironment environment){_options = optionsAccessor.Value;_environment = environment;}public Task InvokeAsync(HttpContext httpContext){var payload = @$"
Environment.ApplicationName: {_environment.ApplicationName}
Environment.EnvironmentName: {_environment.EnvironmentName}
Environment.ContentRootPath: {_environment.ContentRootPath}
Environment.WebRootPath: {_environment.WebRootPath}
Foo: {_options.Foo}
Bar: {_options.Bar}
Baz: {_options.Baz}
";return httpContext.Response.WriteAsync(payload);}
}public class FoobarbazOptions
{public string Foo { get; set; } = default!;public string Bar { get; set; } = default!;public string Baz { get; set; } = default!;
}

我们会利用与当前“承载环境”对应配置来绑定配置选项FoobarbazOptions,后者的三个属性分别来源于三个独立的配置文件。其中settings.json被所有环境共享,settings.dev.json针对名为“dev”的开发环境。我们为承载环境提供更高的要求,在环境基础上进步划分子环境,settings.dev.dev1.json针对的就是dev下的子环境dev1。针对子环境的设置需要利用上述的承载配置来提供。

f658e235facba65aeb969237a48d488f.png

如下所示的就是上述三个配置文件的内容。如果当前环境和子环境分别为dev和dev1,那么配置选项FoobarbazOptions的内容将来源于这三个配置文件。细心的朋友可能还注意到了:我们并没有放在默认的根目录下,而是放在创建的resources目录下,这是因为我们需要利用针对承载环境的设置改变ASP.NET Core应用存放内容文件和Web资源文件的根目录。

settings.json
{"Foo": "123"
}settings.dev.json
{"Bar": "abc"
}settings.dev.dev1.json
{"Baz": "xyz"
}

如下的应用承载程序涵盖了上述的5种初始化操作。中间件的注册通过调用IWebHostBuilder的Configure方法来完成,该方法的参数类型为Action<IApplicationBuilder>,中间件就是通过调用UseMiddleware<TMiddleware>方法注册到IApplicationBuilder对象上。IWebHostBuilder并未对承载配置定义专门的方法,但是我们可以利用UseSettings方法以键值对的形式对其进行设置,这里我们采用这种方式完成了针对“环境”、“内容文件根目录”、“Web资源文件根目录”和“子环境”的设置,前三个是“承载环境”的三个重要属性。承载配置最终会体现到表示承载上下文的WebHostBuilderContext对象上。

using App;
new WebHostBuilder().UseKestrel().UseSetting(WebHostDefaults.EnvironmentKey,"dev").UseSetting(WebHostDefaults.ContentRootKey , Path.Combine(Directory.GetCurrentDirectory(), "resources")).UseSetting(WebHostDefaults.WebRootKey, Path.Combine(Directory.GetCurrentDirectory(), "resources", "web")).UseSetting("SubEnvironment", "dev1").ConfigureAppConfiguration((context, configBuilder) => configBuilder.AddJsonFile(path: "settings.json", optional: false).AddJsonFile(path: $"settings.{context.HostingEnvironment.EnvironmentName}.json", optional: true).AddJsonFile(path: $"settings.{context.HostingEnvironment.EnvironmentName}.{context.Configuration["SubEnvironment"]}.json", optional: true)).ConfigureServices((context, services) => services.AddSingleton<IHandler, Handler>().Configure<FoobarbazOptions>(context.Configuration)).Configure(app => app.UseMiddleware<FoobarMiddleware>()).Build().Run();

依赖服务利用IWebHostBuilder的ConfigureServices方法进行注册,该方法的参数类型为Action<WebHostBuilderContext, IServiceCollection>,意味着我们可以针对之前提供的承载配置(比如承载环境)进行针对性的服务注册。在这里我们不仅注册了依赖服务Handler,还利用当前配置对配置选项FoobarbazOptions实施了绑定。应用配置通过专门的方法ConfigureAppConfiguration进行设置,该方法的参数类型为Action<WebHostBuilderContext, IConfigurationBuilder>,意味着承载配置依然可以利用WebHostBuilderContext上下文获取到,这里我们这是利用它得到对当前环境匹配的三个配置文件。程序启动后,请求可以得到如下的响应内容。

fbf54fe76867916f3dcfa25589c02ad5.png

利用环境变量和命令行参数

由于ASP.NET Core应用在启动时会使用前缀为“ASPNETCORE_”的环境变量作为承载配置,所以上述利用UseSettings方法针对承载配置的设置都可以按照如下的方式利用环境变量代替。

Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", "dev");
Environment.SetEnvironmentVariable("ASPNETCORE_SUBENVIRONMENT", "dev1");
Environment.SetEnvironmentVariable("ASPNETCORE_CONTENTROOT", Path.Combine(Directory.GetCurrentDirectory(), "resources"));
Environment.SetEnvironmentVariable("ASPNETCORE_WEBROOT", Path.Combine(Directory.GetCurrentDirectory(), "resources", "web"));WebHost.CreateDefaultBuilder(args).ConfigureAppConfiguration((context, configBuilder) => configBuilder.AddJsonFile(path: "settings.json", optional: false).AddJsonFile(path: $"settings.{context.HostingEnvironment.EnvironmentName}.json", optional: true).AddJsonFile(path: $"settings.{context.HostingEnvironment.EnvironmentName}.{context.Configuration["SubEnvironment"]}.json", optional: true)).ConfigureServices((context, services) => services.AddSingleton<IHandler, Handler>().Configure<FoobarbazOptions>(context.Configuration)).Configure(app => app.UseMiddleware<FoobarMiddleware>()).Build().Run();

上面的代码片段并没有直接创建WebHostBuilder对象,而是调用WebHost的静态方法CreateDefaultBuilder方法创建了一个具有默认配置的IWebHostBuilder对象。由于该方法传入了命令行参数args,它会将命令行参数作为承载配置源之一,所以程序中四个针对承载配置选项也可以利用命令行参数来完成。

承载环境设置方法

其实承载环境(环境名称、内容文件根目录和Web资源文件根目录)具有专门的方法,所以最方便的还是直接按照如下的方式调用这些方法对它们进行设置。对于我们演示的实例来说,针对环境名称、内容文件和Web资源文件根目录的设置可以直接调用IWebHostBuilder的UseEnvironment、UseContentRoot和UseWebRoot扩展方法来完成。

WebHost.CreateDefaultBuilder(args).UseEnvironment("dev").UseContentRoot(Path.Combine(Directory.GetCurrentDirectory(), "resources")).UseWebRoot(Path.Combine(Directory.GetCurrentDirectory(), "resources", "web")).UseSetting("SubEnvironment", "dev1").ConfigureAppConfiguration((context, configBuilder) => configBuilder.AddJsonFile(path: "settings.json", optional: false).AddJsonFile(path: $"settings.{context.HostingEnvironment.EnvironmentName}.json", optional: true).AddJsonFile(path: $"settings.{context.HostingEnvironment.EnvironmentName}.{context.Configuration["SubEnvironment"]}.json", optional: true)).ConfigureServices((context, services) => services.AddSingleton<IHandler, Handler>().Configure<FoobarbazOptions>(context.Configuration)).Configure(app => app.UseMiddleware<FoobarMiddleware>()).Build().Run();

使用Startup类型

为了不让应用承载程序代码显得过于臃肿,我们一般都会将服务注册和中间件注册移到按照约定定义的Startup类型中。如下面的代码片段所示,中间件和服务注册分别实现在Startup类型的ConfigureServices和Configure方法中,我们直接在构造函数中注入IConfiguration对象得到承载配置对象。值得一提,对于第一代应用承载方式,我们可以在Startup类型的构造函数中注入通过调用IWebHostBuilder的ConfigureServices方法注册的任何服务(包括ASP.NET Core内部通过调用这个方法注册的服务,比如本例的IConfiguration对象)。Startup类型只需要调用IWebHostBuilder的UseStartup<TStartup>扩展方法进行注册即可。

WebHost.CreateDefaultBuilder(args).UseEnvironment("dev").UseContentRoot(Path.Combine(Directory.GetCurrentDirectory(), "resources")).UseWebRoot(Path.Combine(Directory.GetCurrentDirectory(), "resources", "web")).UseSetting("SubEnvironment", "dev1").ConfigureAppConfiguration((context, configBuilder) => configBuilder.AddJsonFile(path: "settings.json", optional: false).AddJsonFile(path: $"settings.{context.HostingEnvironment.EnvironmentName}.json", optional: true).AddJsonFile(path: $"settings.{context.HostingEnvironment.EnvironmentName}.{context.Configuration["SubEnvironment"]}.json", optional: true)).UseStartup<Startup>().Build().Run();public class Startup
{public IConfiguration Configuration { get; }public Startup(IConfiguration configuration) => Configuration = configuration;public void ConfigureServices(IServiceCollection services) => services.AddSingleton<IHandler, Handler>().Configure<FoobarbazOptions>(Configuration);public void Configure(IApplicationBuilder app) => app.UseMiddleware<FoobarMiddleware>();
}

三、第二代应用承载模型

除了承载Web应用,我们还有很多针对后台服务(比如很多批处理任务)的承载需求,为此微软推出了以IHostBuilder/IHost为核心的服务承载系统。Web应用本身实际上就是一个长时间运行的后台服务,我们完全可以将应用定义成一个IHostedService服务,该类型就是下图所示的GenericWebHostService。如果将上面介绍的称为第一代应用承载模式的话,这就是第二代承载模式。

f07756ebe1ddc8b69e4ab5873b9c97da.png

基本编程模式

和所有的Builder模式一样,绝大部分API都落在作为构建者的IHostBuilder接口上,服务注册、承载配置、应用配置都具有对应的方法。由于中间件隶属于GenericWebHostService这一单一的承载服务,所以只能记住与IWebHostBuilder。如果采用第二代应用承载模型,上面演示的程序可以改写成如下的形式。

Host.CreateDefaultBuilder().ConfigureHostConfiguration(config => config.AddInMemoryCollection(new Dictionary<string, string> {[WebHostDefaults.EnvironmentKey] = "dev",[WebHostDefaults.ContentRootKey] = Path.Combine(Directory.GetCurrentDirectory(), "resources"),[WebHostDefaults.WebRootKey] = Path.Combine(Directory.GetCurrentDirectory(), "resources","web"),["SubEnvironment"] = "dev1"})).ConfigureAppConfiguration((context, configBuilder) => configBuilder.AddJsonFile(path: "settings.json", optional: false).AddJsonFile(path: $"settings.{context.HostingEnvironment.EnvironmentName}.json", optional: true).AddJsonFile(path: $"settings.{context.HostingEnvironment.EnvironmentName}.{context.Configuration["SubEnvironment"]}.json", optional: true)).ConfigureServices((context,services) => services.AddSingleton<IHandler, Handler>().Configure<FoobarbazOptions>(context.Configuration)).ConfigureWebHost(webHostBuilder => webHostBuilder.Configure(app=>app.UseMiddleware<FoobarMiddleware>())).Build().Run();

如上面的代码片段所示,我们通过调用Host的静态方法CreateDefaultBuilder方法创建一个具有默认配置的IHostBuidler对象。IHostBuilder为承载配置的设置提供了独立的ConfigureHostConfiguration方法,该方法的参数类型为Action<IConfigurationBuilder>,我们演示的例子利用这个方法注册了一个基于内存字典的配置源,承载环境(环境名称、内容文件和Web资源文件根目录)和子环境名称在这里进行了设置。针对应用配置的设置通过ConfigureAppConfiguration方法来完成,该方法的参数类型为Action<HostBuilderContext, IConfigurationBuilder>,代表承载上下文的HostBuilderContext可以得到预先设定的承载环境和承载配置,我们的例子利用到定位与当前环境相匹配的配置文件。

IHostBuilder同样定义了ConfigureServices方法,该方法的参数类型为Action<HostBuilderContext, IServiceCollection>,意味着服务依然可以针对承载环境和承载配置进行注册。由于中间件的注册依然落在IWebHostBuilder上,所以IHostBuilder提供了ConfigureWebHost/ConfigureWebHostDefaults这两个扩展方法予以适配,它们具有一个类型为Action<IWebHostBuilder>的参数。

承载环境设置方法

和IWebHostBuilder一样,IHostBuidler同样提供了用来直接设置承载环境的方法。对于我们演示的实例来说,针对环境名称、内容文件和Web资源文件根目录的设置可以直接调用IHostBuidler的UseEnvironment、UseContentRoot和UseWebRoot扩展方法来完成。由于Web资源文件并未“服务承载”的范畴,所以针对Web资源文件根目录的设置还得采用直接设置承载配置的方式(或者调用IWebHostBuilder的UseWebRoot扩展方法)。

Host.CreateDefaultBuilder().UseEnvironment("dev").UseContentRoot(Path.Combine(Directory.GetCurrentDirectory(), "resources")).ConfigureHostConfiguration(config => config.AddInMemoryCollection(new Dictionary<string, string> {[WebHostDefaults.WebRootKey] = Path.Combine(Directory.GetCurrentDirectory(), "resources","web"),["SubEnvironment"] = "dev1"})).ConfigureAppConfiguration((context, configBuilder) => configBuilder.AddJsonFile(path: "settings.json", optional: false).AddJsonFile(path: $"settings.{context.HostingEnvironment.EnvironmentName}.json", optional: true).AddJsonFile(path: $"settings.{context.HostingEnvironment.EnvironmentName}.{context.Configuration["SubEnvironment"]}.json", optional: true)).ConfigureServices((context,services) => services.AddSingleton<IHandler, Handler>().Configure<FoobarbazOptions>(context.Configuration)).ConfigureWebHost(webHostBuilder => webHostBuilder.Configure(app=>app.UseMiddleware<FoobarMiddleware>())).Build().Run();

针对IWebHostBuilder的适配

由于IHostBuilder利用扩展方法ConfigureWebHost/ConfigureWebHostDefaults提供了针对IWebHostBuilder的适配,意味着前面采用第一代应用承载方法编写的代码可以直接移植过来。如下面的代码片段所示,静态方法ConfigureWebHost完全依然利用IWebHostBuilder完成所有的初始化工作,我们只需要将指向该方法的Action<IWebHostBuilder>委托传入IHostBuilder的ConfigureWebHostDefaults扩展方法就可以了。

Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(ConfigureWebHost).Build().Run();static void ConfigureWebHost(IWebHostBuilder webHostBuilder)
{webHostBuilder.UseEnvironment("dev").UseContentRoot(Path.Combine(Directory.GetCurrentDirectory(), "resources")).UseWebRoot(Path.Combine(Directory.GetCurrentDirectory(), "resources", "web")).UseSetting("SubEnvironment", "dev1").ConfigureAppConfiguration((context, configBuilder) => configBuilder.AddJsonFile(path: "settings.json", optional: false).AddJsonFile(path: $"settings.{context.HostingEnvironment.EnvironmentName}.json", optional: true).AddJsonFile(path: $"settings.{context.HostingEnvironment.EnvironmentName}.{context.Configuration["SubEnvironment"]}.json", optional: true)).UseStartup<Startup>();
}public class Startup
{public IConfiguration Configuration { get; }public Startup(IConfiguration configuration) => Configuration = configuration;public void ConfigureServices(IServiceCollection services) => services.AddSingleton<IHandler, Handler>().Configure<FoobarbazOptions>(Configuration);public void Configure(IApplicationBuilder app) => app.UseMiddleware<FoobarMiddleware>();
}

Startup构造函数注入的限制

第二代应用承载模型利用ConfigureWebHost/ConfigureWebHostDefaults扩展方法对之前定义在IWebHostBuilder上的API(绝大部分是扩展方法)提供了100%的支持(除了Build方法),但是针对Startup构造函数中注入的服务则不再那么自由。如果采用基于IWebHostBuilder/IWebHost的应用承载方式,通过调用IWebHostBuilder的ConfigureServices方法注册的服务都可以注入Startup的构造函数中,如果采用基于IHostBuilder/IHost的应用承载方式,只有与“承载配置(承载环境属于承载配置的一部分)”相关的如下三个服务能够注入到Startup的构造函数中。

  • IHostingEnvironment

  • IWebHostEnvironment

  • IHostEnvironment

  • IConfiguration

对于如下这段代码,虽然注入Startup构造函数的Foobar同时通过调用IHostBuilder和IWebHostBuilder的ConfigureServices方法中进行了注册,但是在创建Startup实例的时候依然会抛出异常。

Host.CreateDefaultBuilder(args).ConfigureServices(sevices=>sevices.AddSingleton<Foobar>()).ConfigureWebHostDefaults(ConfigureWebHost).Build().Run();static void ConfigureWebHost(IWebHostBuilder webHostBuilder)
{webHostBuilder.UseEnvironment("dev").ConfigureServices(sevices => sevices.AddSingleton<Foobar>()).UseContentRoot(Path.Combine(Directory.GetCurrentDirectory(), "resources")).UseWebRoot(Path.Combine(Directory.GetCurrentDirectory(), "resources", "web")).UseSetting("SubEnvironment", "dev1").ConfigureAppConfiguration((context, configBuilder) => configBuilder.AddJsonFile(path: "settings.json", optional: false).AddJsonFile(path: $"settings.{context.HostingEnvironment.EnvironmentName}.json", optional: true).AddJsonFile(path: $"settings.{context.HostingEnvironment.EnvironmentName}.{context.Configuration["SubEnvironment"]}.json", optional: true)).UseStartup<Startup>();
}public class Startup
{public IConfiguration Configuration { get; }public Startup(IConfiguration configuration,Foobar foobar) => Configuration = configuration;public void ConfigureServices(IServiceCollection services) => services.AddSingleton<IHandler, Handler>().Configure<FoobarbazOptions>(Configuration);public void Configure(IApplicationBuilder app) => app.UseMiddleware<FoobarMiddleware>();
}public class Foobar
{ }

综上所述,最初版本的ASP.NET Core由于只考虑到Web应用自身的承载,所以设计出了基于IWebHostBuilder/IWebHost模型。后来产生了基于后台服务承载的需求,所以推出了基于IHostBuilder/IHost的服务承载模型,原本的Web应用作为一个“后台服务(GenericWebHostService.)”进行承载。由于之前很多API都落在IWebHostBuilder(主要无数的扩展方法),出于兼容性的需求,一个名为GenericWebHostBuilder的实现类型被定义出来,它将针对IWebHostBuilder的方法调用转移到IHostBuilder/IHost的服务承载模型中。

.NET 6在IHostBuilder/IHost服务承载模型基础上推出了更加简洁的Minimal API,此时又面临相同的“抉择”。这次它不仅需要兼容IWebHostBuilder,还得兼容IHostBuilder,再加上Minimal API自身提供的API,所以“一题多解”的现象就更多了。如果你对ASP.NET Core的历史不甚了解,将会感到非常困惑。令你们更加感到困惑的时,此时定义在IWebHostBuilder和IHostBuilder的API并非全部可用,本文的下篇将为你一一解惑。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/281843.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

WPF 简单模仿 VSCode 界面布局

WPF 简单模仿 VSCode 界面布局本文经原作者授权以原创方式二次分享&#xff0c;欢迎转载、分享。WPF 简单模仿 VSCode 界面布局作者&#xff1a;弈虎-吕女士会翻墙原文链接&#xff1a; https://github.com/Chen-Lin-Zhao-Wei/WPFLikeVSCode分享一篇群友这几天自己写的 WPF 简…

compare()方法+使用compare方法

compare()方法 compare(lob1,lob2,amount,offset_1,offset_2) 1用于比较2个lob存储的数据&#xff0c;比较的方式是从指定偏移量开始&#xff0c;对指定数量的字符或者字节进行比较。 2如果比较内容相同&#xff0c;返回0&#xff0c;否则返回-1或1. 3如果参数设置有误或不合…

linux 下 mysql默认表_linux环境下mysql默认是区分表名大小写的

在linux环境下&#xff0c;mysql默认表明是区分大小写的&#xff0c;我们可以查看全局变量发现:mysql> show variables like lower%;-------------------------------| Variable_name | Value |-------------------------------| lower_case_file_system | OFF || lower_cas…

两将军问题和TCP三次握手

两将军问题&#xff0c;又被称为两将军悖论、两军问题&#xff0c; 是一个经典的计算机思想实验。首先&#xff0c; 为避免混淆&#xff0c;我们需要认识到两将军问题虽然与拜占庭将军问题相关&#xff0c;但两者不是一个东西。拜占庭将军问题是一个更通用的两将军问题版本&…

微信小程序开发系列五:微信小程序中如何响应用户输入事件

2019独角兽企业重金招聘Python工程师标准>>> 微信小程序开发系列教程 微信小程序开发系列一&#xff1a;微信小程序的申请和开发环境的搭建 微信小程序开发系列二&#xff1a;微信小程序的视图设计 微信小程序开发系列三&#xff1a;微信小程序的调试方法 微信小程序…

理解Object.defineProperty的作用

Object.defineProperty 是vue中双向绑定的基础。vue是通过数据劫持的方式来做数据绑定的&#xff0c;最核心的方法是通过 Object.defineProperty()方法来实现对属性的劫持&#xff0c;达到能监听到数据的变动。要实现数据的双向绑定&#xff0c; 当使用存取器描述属性的特性的时…

直播修仙:使用.NET 的 WebView2 如何获取请求的响应内容,以微信直播的互动直播为例...

背景近几年直播行业快速发展&#xff0c;门槛也越来越低&#xff0c;越来越的人涌入直播大军。不得不说&#xff0c;直播不仅带来了更多的娱乐消遣&#xff0c;还提供了一个新型的就业方式。说起直播的类型&#xff0c;有一个非常小众的娱乐直播&#xff0c;没有主播&#xff0…

web第6次作业position

position 属性指定了元素的定位类型。 position 属性的五个值&#xff1a; static &#xff08;静态定位&#xff09; HTML元素的默认值&#xff0c;即没有定位&#xff0c;元素出现在正常的流中。 静态定位的元素不会受到 top, bottom, left, right影响。 div.stati…

GeneralUpdate版本更新公告20221009

大家好我是juster&#xff0c;GeneralUpdate的开源项目作者。这次将发布GeneralUpdate兼容.NET MAUI和多平台为核心的版本。经过国庆假期的打磨修复了大量开源社区开发者的提交的bug和不合理修改建议&#xff0c;重构、删除了大量代码和结构使用和上一个版本没有太大变化。1.更…

实验2 java_《Java程序设计》实验2

1、使用java语言编程&#xff0c;从键盘输入N个整数存储到数组中&#xff0c;求数组所有元素的和、最大值和平均值。import java.util.Scanner;public class Program01{public static void main(String [] args){Scanner scanner new Scanner(System.in);System.out.println(&…

WPF遍历当前容器中某种控件的方法

原文:WPF遍历当前容器中某种控件的方法版权声明&#xff1a;本文为博主原创文章&#xff0c;未经博主允许不得转载。 https://blog.csdn.net/m0_37591671/article/details/79528845 WPF遍历当前容器中某种控件的方法 WPF遍历当前容器中某种控件的方法1.目的&#xff1a;2.实现思…

善用Object.defineProperty巧妙找到修改某个变量的准确代码位置

2019独角兽企业重金招聘Python工程师标准>>> 我今天的工作又遇到一个难题。前端UI右下角这个按钮被设置为"禁用(disabled)"状态。 这个按钮的可用状态由属性enabled控制。我通过调试发现&#xff0c;一旦下图第88行代码执行完毕之后&#xff0c;这个按钮的…

使用 C# 开发的轻量级开源数据库 LiteDB

你好&#xff0c;这里是 Dotnet 工具箱&#xff0c;定期分享 Dotnet 有趣&#xff0c;实用的工具或组件&#xff0c;希望对您有用&#xff01;简介 LiteDB 是一个小型、快速、轻量级的 .NET NoSQL 嵌入式数据库&#xff0c;也就是我们常说的 K/V 数据库&#xff0c;完全用 C# …

微信小程序仿微信SlideView组件slide-view

微信小程序仿微信SlideView组件。 使用 1、安装 slide-view 从小程序基础库版本 2.2.1 或以上、及开发者工具 1.02.1808300 或以上开始&#xff0c;小程序支持使用 npm 安装第三方包。 npm install --save miniprogram-slide-view2、在需要使用 slide-view 的页面 page.json 中…

hibernate 环境搭建测试

对于hibernate的介绍&#xff0c;网络上一搜一堆&#xff0c;恐怕我写的也没前辈总结的好。这个博主总结的十分好,方便大家欣赏 http://blog.csdn.net/liujiahan629629/article/details/21442607 真正要掌握&#xff0c;还得需要自己动手&#xff0c;才能丰衣足食。所需jar包j…

C# WPF 中使用 MahApps.Metro.IconPacks 提供的图标

概述我们在桌面应用程序开发时经常会用到很多图标&#xff0c;时常我是在阿里矢量图库下载&#xff1a;https://www.iconfont.cn/&#xff0c;然后存放多项目中去引用&#xff0c;不过这样操作起来有点繁琐&#xff0c;这节我们介绍一个更加便捷的方式.用法概述Wpf 图标管理工具…

java多线程同时运行_Java实现的两个线程同时运行案例

本文实例讲述了Java实现的两个线程同时运行。分享给大家供大家参考&#xff0c;具体如下&#xff1a;/*** 两个案例同时运行案例* 1:这个两个线程并不是有规律的运行而是有没有规律的交替运行*/package com.test3;public class Demo10_3 {/*** param args*/public static void …

dotnet 用 SourceGenerator 源代码生成技术实现中文编程语言

相信有很多伙伴都很喜欢自己造编程语言&#xff0c;在有现代的很多工具链的帮助下&#xff0c;实现一门编程语言&#xff0c;似乎已不是一件十分困难的事情。我利用 SourceGenerator 源代码生成技术实现了一个简易的中文编程语言&#xff0c;核心原理是将中文编程语言翻译为 C#…

HTTP/2 规格制定完成

IETF HTTP工作者的负责人Mark Nottingham在其博客上宣布HTTP/2规格制定完成&#xff0c;接下来将是分配RFC编号和正式发表。HTTP是Web的核心技术之一&#xff0c;相比HTTP/1&#xff0c;HTTP/2的改进之处包括更快的页面加载&#xff1b;更长久的连接&#xff1b;服务器推送&…

easyui combobox java_Easyui的combobox实现动态数据级联效果

实现从数据库中动态获取对应的list集合&#xff0c;并在easyui的combobox中显示出来。实现的效果如下&#xff1a;1、数据库的表设计如图所示2、数据库中填写相关的数据&#xff0c;如图所示。如图所示【法律法规】是所属栏目&#xff0c;因此他的字段parentid是0。【中国公民出…