2023年4月

哈喽大家好,我是咸鱼


《Flask Web 开发指南 pt.1》
中,咸鱼跟大家介绍了 Flask 的由来——诞生于一个愚人节玩笑,简单介绍了一些关于 Flask 的概念,并且编写了一个简单的 Flask 程序

在编写 Flask 程序的时候,你需要注意你的程序文件不要命名为 flask.py,建议命名为 app.py 或者 wsgi.py

但如果你的程序不是叫 app.py 或者 wsgi.py,那么你就需要设置环境变量 FLASK_APP 的值为程序名字

设置环境变量有两种方法,在《开发指南 pt.1》有提到,大家可以去看一下

运行环境

我们知道,代码的运行环境一般分为
开发环境和生产环境

开发环境指的是我们在本地编写和测试程序时的计算机环境

而生产环境也叫线上环境,指的是程序部署在互联网能够对外提供服务且供用户使用的环境

在 Flask 中,Flask 会根据运行环境的不同来改变相应的行为和设置,建议小伙伴们在编写 Flask 程序的时候将运行环境设置成开发环境

因为在开发环境下,Flask 会开启调试模式(Debug Mode),这时候执行
flask run
启动程序会激活 Werkzeug 内置的调试器和重载器,这能更好地帮助我们去 debug

  • 调试器(debugger)

当你的程序报错时,网页会生成详细的错误信息,而调试器允许你在错误页面上执行代码

首先单击错误信息右侧的命令行图标,然后会弹出一个窗口要求你输入 PIN 码,这个 PIN 码在你执行 flask run 命令的时候程序会给你

输入 PIN 码之后,点击错误堆栈的某个节点右侧的命令行图标,就会出现一个包含代码执行上下文信息的 Python Shell

  • 重载器(reloader)

重载器的作用就是检测程序文件的变动,然后重启。当修改了代码之后,你会在终端看到下面的输出

这意味着每次修改完代码不需要手动重启 flask 程序

PS

网上有些 flask 教程可能告诉你按照下面的方式将 Flask 运行环境将设置成开发环境

这个环境变量在早期版本的 flask 中是适用的,但是后面被弃用了,
不建议使用这个环境变量,否则可能会 warning


相关链接:https://github.com/docker/docs/issues/15571

URL 路由

注册路由

在上面这段代码中,我们通过 route() 装饰器将根地址与 index() 函数绑定在一起,当用户访问

http://localhost:5000/
的时候,就会显示 index 函数的返回结果
'<h1>Hello,Flask!</h1>'

通过 route() 装饰器将 URL 与函数建立关联的这个过程叫做注册路由
,路由负责管理 URL 与函数之间的映射

即我访问哪个 URL (前提是这个 URL 已经与函数建立起了关联),Flask 就去对应的函数下执行并返回结果给我

这个函数被称作视图函数
,视图函数返回的结果不仅仅是一串字符串这么简单,它还能够返回各种链接、表单、HTML 文件等等

上面的代码中,'/' 为相对 URL,index() 函数为视图函数

一个 Web 程序其实就是用户点击不同的 URL 链接,程序内部去执行对应的函数然后返回对应的结果给用户。可以说,大部分 Flask 的基本模式就是这一个个注册路由的实现

为一个视图函数绑定多个 URL

现在无论是访问
http://localhost:5000/

http://localhost:5000/home
还是
http://localhost:5000/index
,返回的都是同一个值

动态URL

除此之外,我们还可以在
URL 里定义变量,实现动态 URL

添加变量使用
<变量名>
的形式

例如
/user/<name>
,flask 在处理请求时会把这个变量传入视图函数里面

上面的代码中 user_page() 视图的 URL 为
/user/<name>
,那么像
/user/edison、/user/fish
的请求都会触发这个函数


但是如果你访问的时候没有添加变量(比如访问
/user
),那么会返回一个 404 错误,该如何解决呢?

常见的方法是设置默认值,当访问的 URL 里面没有带变量时,就会使用默认值。设置默认值有两种方法:

  • 在装饰器里面使用 default 参数添加默认值

请注意,你需要添加两条 URL 规则,这样当用户访问
/user
时 flask 才能找到对应的视图函数

  • 在视图函数里面添加默认值

对于 URL 变量,flask 支持在 URL 里对变量设置处理器,对变量进行预处理

转换器类型:

关于 URL 后面带斜杠的问题

这是一个常见但是也容易混淆的问题

上面的代码里面,这两条 URL 的尾部一个添加了斜杠一个没有添加,有什么区别呢?

  • projects() 视图函数的 URL 尾部有一个斜杠,路径看起来像一个目录。如果你访问
    /projects
    ,Flask 会自动帮你重定向到
    /projects/
    ,即无论是访问
    /projects/
    还是
    /projects
    ,都能正常返回

  • about() 视图函数的 URL 尾部没有斜杠,路径看起来像一个文件。如果你访问
    /about/
    ,Flask 会返回一个 404 错误。因为这样可以保持 URL 唯一,并帮助搜索引擎避免重复索引同一页面

端点

上面我们讲过,在编写 Flask 程序的时候会将 URL 与 视图函数进行关联(注册路由)

即通过将不同 URL 关联到不同的视图函数上,以此来实现不同的业务逻辑

但其实在 Flask 中,URL 首先被映射到端点上,然后再映射到视图函数

端点通常用来标记一个视图函数以及对应的 URL,默认值是视图函数的函数名

在上面的代码中,端点即视图函数的名称hello

当然你也可以修改端点名(通常不建议这么做)

当访问
hhttp://localhost:5000/hello
时,URL 首先映射到端点
say_hello
上,然后与端点
say_hello
相关联的视图函数
hello()
就会处理这个请求

在 web 程序中,URL 无处不在。如果我们在编写 web 程序的时候使用 URL 都是用硬编码方式,那么就会大大降低代码的易用性

硬编码是指在写代码过程中将变量用一个固定数值表示,如果后续想要修改这个变量就非常困难

比如在 flask 中,如果你修改了某个路由的 URL,那么在其他调用到这个 URL 的视图函数里面的 URL 都需要修改,很麻烦

一个常用的解决方法就是使用 Flask 中的 url_for() 函数来获取 URL

url_for() 函数中第一个参数为端点,这说明端点通常用作反向查询 URL 地址

在上面的代码中,端点即视图函数的函数名 index,通过调用 url_for() 函数就可以获得对应的 URL

如果你的视图函数绑定的是动态 URL,那么需要在 url_for() 函数中添加相关参数

上面所说的 URL 都是相对 URL(URL 中的 path 部分),如果你想获得绝对 URL,需要在 url_for() 函数中添加相关参数
_external=True


感谢阅读,
喜欢作者就动动小手

[
一键三连]

,这是我
写作
最大的动力!

人工智能的概念

当人们提到“人工智能(AI)”时,很多人会想到机器人和未来世界的科幻场景,但AI的应用远远不止于此。现在,AI已经广泛应用于各种行业和生活领域,为我们带来了无限可能。

AI是一个广泛的概念,它包括很多不同的技术,例如机器学习、深度学习、自然语言处理等等。这些技术都具有不同的功能和应用。

机器学习是一种基于数据的人工智能技术,它可以让计算机根据大量的数据进行自我学习和优化。通过机器学习,计算机可以不断地  “感知”  与之相关的数据,并从中不断学习、发掘数据中的规律和关联,最终能够生成预测模型,并在接受新的数据时,更新和优化模型。这项技术在推荐系统、航空航天、语音识别、图像识别、金融服务等领域有着广泛的应用。

深度学习是机器学习的一种更高级的形式,它模仿人类神经元的运作模式,通过神经网络层层递归,将输入的数据不断地处理和推理以达到最终的输出结果。深度学习技术在语音和图像识别、自然语言理解等领域都有着广泛的应用。例如,在语音识别领域,深度学习技术已经成为了语音识别系统的主要工具。它可以对话者的说话速度、音量、发音等因素进行自适应,准确识别出话语的内容。

自然语言处理(NLP)是一项将人类语言转换为计算机语言的技术,它可以分析、识别、理解和生成人类语言,实现与人类自然语言的交互。NLP  目前已经应用于许多领域,例如智能客服、机器翻译、个性化推荐、信息抽取等等。

总之,随着技术的不断进步,AI  技术已经成为了推动社会进步和提升人类生活质量的核心技术之一。未来,我们还有很大的发展空间和应用前景,可以期待  AI  技术在未来的发展中,为我们带来更多的惊喜和改变。

迎接ChatGPT的到来

当今互联网时代,沟通变得更为方便快捷。而ChatGPT作为一款新兴应用,也让人们的沟通更加便捷、简单。

ChatGPT是一个基于人工智能技术的在线聊天工具。该工具利用自然语言处理技术对用户输入的文本信息进行理解和解析,然后通过自动生成的回复来与用户进行互动。ChatGPT一般会先从用户的输入中提取主要信息,了解用户的意图和需要,然后按照设定的流程来回复用户。

ChatGPT的优点之一是它可以快速地回应用户的要求,让用户不必等待太久。同时,该应用的人工智能技术也保证了其回复的质量和准确性。除此之外,ChatGPT还可以通过学习用户的输入和反馈来不断提升自己的表现,更好地满足用户的需求。

ChatGPT在应用场景方面也很广泛。例如,它可以应用于在线客服、智能客户关系管理、自然语言问答系统、智能机器人等领域。无论在商业还是个人方面,ChatGPT都能为用户带来非常便捷的在线沟通体验。

总的来说,ChatGPT是一个强大、高效、易用的在线聊天工具,它与人工智能技术的结合为用户提供了更高品质的沟通服务。未来,随着技术的不断发展,ChatGPT也将会不断更新和升级,为用户带来更多的惊喜和便捷。

AI未来的方向包括以下几个方面:

1. 更高效的机器学习算法和技术:机器学习是AI的基石,未来的AI将会拥有更高效、更强大的机器学习算法和技术。

2. 自动化和智能化的工作流程:AI将会带来更智能、更高效的工作流程,帮助人们完成更多的重复性工作,并提高生产效率。

3. 更加智能化的智能设备:未来的AI会不断完善智能设备,使其更加智能、更加个性化,更好地服务于人类。

4. 更加智能化的城市和社区:AI还将改变城市和社区的面貌,引导城市和社区朝着更加智能化、更加可持续的方向发展。

5. 更加智能化的医疗和健康:AI将提供更多的健康服务,包括预测疾病、诊断疾病、个性化医疗等,将有助于提高健康水平,帮助人们更好地管理自己的健康。

总之,未来的AI将发挥更加强大的作用,为人类带来更多的便利和改变。

AIGC未来的发展方向

AIGC作为一个全球性的人工智能研究和应用组织,在未来有以下发展方向:

  1. 深入研究人工智能技术,探索新的应用场景。AIGC将持续研究人工智能领域的前沿技术,同时探索人工智能在更广泛的领域中的应用。

  2. 加强跨行业合作,推动智能化产业发展。 AIGC将与各行各业的企业和组织建立合作关系,共同推动各行业的智能化发展,同时提高人工智能的普及度和应用价值。

  3. 提高数据安全和隐私保护水平。AIGC将致力于开发更加安全和隐私友好的人工智能技术,同时提高数据的安全性和保护用户的隐私。

  4. 推广人工智能教育和普及。 AIGC将不断推广人工智能相关的教育和培训活动,让更多的人了解和掌握人工智能技术,同时推进普及化的应用。

  5. 提升全球视野和国际合作。AIGC将持续关注全球人工智能技术的发展和创新,积极拓展国际合作关系,加强与其他组织和机构的交流和合作。

摘要:基于.NET Core 7.0WebApi后端架构实战【2-实现动态路由与Dynamic API】  2023/02/22, ASP.NET Core 7.0, VS2022

引言

    使用过ABP vNext和Furion框架的可能都会对它们的动态API感到好奇,不用手动的去定义,它会动态的去创建API控制器。后端代码
架构的复杂在核心代码,如果这些能封装的好提升的是小组整体的生产力。灵图图书的扉页都会有这样一句话:"站在巨人的肩膀上"。我在
这里大言不惭的说上一句我希望我也能成为"巨人"!

动态路由

在.Net Core WebAPI程序中通过可全局或局部修改的自定义Route属性和URL映射组件匹配传入的HTTP请求替代默认路由即为动态路由

WebApplicationBuilder

在3.1以及5.0的版本中,Configure方法中会自动添加UseRouting()与UseEndpoints()方法,但是在6.0以上版本已经没有了。其实在创建WebApplicationBuilder实例的时候默认已经添加进去了。请看源码:

var builder = WebApplication.CreateBuilder(args);

WebApplication.cs文件中

/// <summary>
/// Initializes a new instance of the class with preconfigured defaults.
/// </summary>
/// <param name="args">Command line arguments</param>
/// <returns>The <see cref="WebApplicationBuilder"/>.</returns>
public static WebApplicationBuilder CreateBuilder(string[] args) =>
    new(new WebApplicationOptions() { Args = args });

WebApplicationBuilder.cs文件中,webHostBuilder.Configure(ConfigureApplication)这句代码他将包含注册路由与终结点的方法添加到了宿主程序启动的配置当中。

internal WebApplicationBuilder(WebApplicationOptions options, Action? configureDefaults = null)
{
    Services = _services;
    var args = options.Args;
    // Run methods to configure both generic and web host defaults early to populate config from appsettings.json
    // environment variables (both DOTNET_ and ASPNETCORE_ prefixed) and other possible default sources to prepopulate
    // the correct defaults.
    _bootstrapHostBuilder = new BootstrapHostBuilder(Services, _hostBuilder.Properties);
    // Don't specify the args here since we want to apply them later so that args
    // can override the defaults specified by ConfigureWebHostDefaults
    _bootstrapHostBuilder.ConfigureDefaults(args: null);
    // This is for testing purposes
    configureDefaults?.Invoke(_bootstrapHostBuilder);
    // We specify the command line here last since we skipped the one in the call to ConfigureDefaults.
    // The args can contain both host and application settings so we want to make sure
    // we order those configuration providers appropriately without duplicating them
    if (args is { Length: > 0 })
    {
        _bootstrapHostBuilder.ConfigureAppConfiguration(config =>
        {
            config.AddCommandLine(args);
        });
    }
    _bootstrapHostBuilder.ConfigureWebHostDefaults(webHostBuilder =>
    {
        // Runs inline.
//看这里 webHostBuilder.Configure(ConfigureApplication); // Attempt to set the application name from options options.ApplyApplicationName(webHostBuilder); }); // Apply the args to host configuration last since ConfigureWebHostDefaults overrides a host specific setting (the application n _bootstrapHostBuilder.ConfigureHostConfiguration(config => { if (args is { Length: > 0 }) { config.AddCommandLine(args); } // Apply the options after the args options.ApplyHostConfiguration(config); }); Configuration = new(); // This is chained as the first configuration source in Configuration so host config can be added later without overriding app c Configuration.AddConfiguration(_hostConfigurationManager); // Collect the hosted services separately since we want those to run after the user's hosted services _services.TrackHostedServices = true; // This is the application configuration var (hostContext, hostConfiguration) = _bootstrapHostBuilder.RunDefaultCallbacks(Configuration, _hostBuilder); // Stop tracking here _services.TrackHostedServices = false; // Capture the host configuration values here. We capture the values so that // changes to the host configuration have no effect on the final application. The // host configuration is immutable at this point. _hostConfigurationValues = new(hostConfiguration.AsEnumerable()); // Grab the WebHostBuilderContext from the property bag to use in the ConfigureWebHostBuilder var webHostContext = (WebHostBuilderContext)hostContext.Properties[typeof(WebHostBuilderContext)]; // Grab the IWebHostEnvironment from the webHostContext. This also matches the instance in the IServiceCollection. Environment = webHostContext.HostingEnvironment; Logging = new LoggingBuilder(Services); Host = new ConfigureHostBuilder(hostContext, Configuration, Services); WebHost = new ConfigureWebHostBuilder(webHostContext, Configuration, Services); Services.AddSingleton(_ => Configuration); }

private void ConfigureApplication(WebHostBuilderContext context, IApplicationBuilder app)
{
    Debug.Assert(_builtApplication is not null);
    // UseRouting called before WebApplication such as in a StartupFilter
    // lets remove the property and reset it at the end so we don't mess with the routes in the filter
    if (app.Properties.TryGetValue(EndpointRouteBuilderKey, out var priorRouteBuilder))
    {
        app.Properties.Remove(EndpointRouteBuilderKey);
    }
    if (context.HostingEnvironment.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    // Wrap the entire destination pipeline in UseRouting() and UseEndpoints(), essentially:
    // destination.UseRouting()
    // destination.Run(source)
    // destination.UseEndpoints()
    // Set the route builder so that UseRouting will use the WebApplication as the IEndpointRouteBuilder for route matching
    app.Properties.Add(WebApplication.GlobalEndpointRouteBuilderKey, _builtApplication);
    // Only call UseRouting() if there are endpoints configured and UseRouting() wasn't called on the global route builder already
    if (_builtApplication.DataSources.Count > 0)
    {
        // If this is set, someone called UseRouting() when a global route builder was already set
        if (!_builtApplication.Properties.TryGetValue(EndpointRouteBuilderKey, out var localRouteBuilder))
        {
//添加路由中间件 app.UseRouting(); } else { // UseEndpoints will be looking for the RouteBuilder so make sure it's set app.Properties[EndpointRouteBuilderKey] = localRouteBuilder; } } // Wire the source pipeline to run in the destination pipeline app.Use(next => { _builtApplication.Run(next); return _builtApplication.BuildRequestDelegate(); }); if (_builtApplication.DataSources.Count > 0) { // We don't know if user code called UseEndpoints(), so we will call it just in case, UseEndpoints() will ignore duplicate DataSources
//添加终结点中间件 app.UseEndpoints(_ => { }); } // Copy the properties to the destination app builder foreach (var item in _builtApplication.Properties) { app.Properties[item.Key] = item.Value; } // Remove the route builder to clean up the properties, we're done adding routes to the pipeline app.Properties.Remove(WebApplication.GlobalEndpointRouteBuilderKey); // reset route builder if it existed, this is needed for StartupFilters if (priorRouteBuilder is not null) { app.Properties[EndpointRouteBuilderKey] = priorRouteBuilder; } }

WebHostBuilderExtensions.cs文件中,Configure方法用于加入配置项,GetWebHostBuilderContext方法用于获取宿主机构建的上下文信息,即已配置的主机信息。

public IWebHostBuilder Configure(Action<WebHostBuilderContext, IApplicationBuilder> configure)
{
    var startupAssemblyName = configure.GetMethodInfo().DeclaringType!.Assembly.GetName().Name!;
    UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName);
    // Clear the startup type
    _startupObject = configure;
    _builder.ConfigureServices((context, services) =>
    {
        if (object.ReferenceEquals(_startupObject, configure))
        {
            services.Configure(options =>
            {
                var webhostBuilderContext = GetWebHostBuilderContext(context);
                options.ConfigureApplication = app => configure(webhostBuilderContext, app);
            });
        }
    });
    return this;
}
private static WebHostBuilderContext GetWebHostBuilderContext(HostBuilderContext context) { if (!context.Properties.TryGetValue(typeof(WebHostBuilderContext), out var contextVal)) { var options = new WebHostOptions(context.Configuration, Assembly.GetEntryAssembly()?.GetName().Name ?? string.Empty); var webHostBuilderContext = new WebHostBuilderContext { Configuration = context.Configuration, HostingEnvironment = new HostingEnvironment(), }; webHostBuilderContext.HostingEnvironment.Initialize(context.HostingEnvironment.ContentRootPath, options); context.Properties[typeof(WebHostBuilderContext)] = webHostBuilderContext; context.Properties[typeof(WebHostOptions)] = options; return webHostBuilderContext; } // Refresh config, it's periodically updated/replaced var webHostContext = (WebHostBuilderContext)contextVal; webHostContext.Configuration = context.Configuration; return webHostContext; }

UseRouting

源码如下图所示:


erifyRoutingServicesAreRegistered
用于验证路由服务是否已注册到容器内部

②判断在请求管道的共享数据字典的Properties中是否有
GlobalEndpointRouteBuilderKey
的键,如果没有则New一个新的终结点路由构建者对象,并将
EndpointRouteBuilder
添加到共享字典中。后面
UseEndpoints(Action<IEndpointRouteBuilder> configure)
执行时,会将前面New的
DefaultEndpointRouteBuilder
实例取出,并进一步配置它:
configure(EndpointRouteBuilder实例)

③将
EndpointRoutingMiddleware
中间件注册到管道中,该中间件根据请求和Url匹配最佳的Endpoint,然后将该终结点交由EndpointMiddleware 处理。

UseEndpoints

源码如下图所示:


VerifyEndpointRoutingMiddlewareIsRegistered
方法将
EndpointRouteBuilder
从请求管道的共享字典中取出,如果没有则说明之前没有调用
UseRouting()
,所以调用
UseEndpoints()
之前要先调用
UseRouting()

VerifyEndpointRoutingMiddlewareIsRegistered
方法如下图所示:


EndpointMiddleware
主要是在
EndpointRoutingMiddleware
筛选出
endpoint
之后,调用该
endpoint

endpoint.RequestDelegate(httpContext)
进行请求处理。并且这个中间件会最终执行RequestDelegate委托来处理请求。请求的处理大部分功能在中间件
EndpointRoutingMiddleware
中,它有个重要的属性
_endpointDataSource
保存了上文中初始化阶段生成的
MvcEndpointDataSource
,而中间件
EndpointMiddleware
的功能比较简单,主要是在
EndpointRoutingMiddleware
筛选出
endpoint
之后,调用该
endpoint.RequestDelegate(httpContext)
方法进行请求处理。

看一下Endpoint类源码,Endpoint就是定义谁(Action)来执行请求的对象

public class Endpoint
{
    ///<summary>
    /// Creates a new instance of.
    ///</summary>
    ///<param name="requestDelegate">The delegate used to process requests for the endpoint.</param>
    ///<param name="metadata">
    /// The endpoint <see cref="EndpointMetadataCollection"/>. May be null.
    ///</param>
    ///<param name="displayName">
    /// The informational display name of the endpoint. May be null.
/// </param> public Endpoint( RequestDelegate? requestDelegate, EndpointMetadataCollection? metadata, string? displayName) { // All are allowed to be null RequestDelegate = requestDelegate; Metadata = metadata ?? EndpointMetadataCollection.Empty; DisplayName = displayName; } /// <summary> /// Gets the informational display name of this endpoint. /// </summary> public string? DisplayName { get; } /// <summary> /// Gets the collection of metadata associated with this endpoint. /// public EndpointMetadataCollection Metadata { get; } /// <summary> /// Gets the delegate used to process requests for the endpoint. /// </summary> public RequestDelegate? RequestDelegate { get; } /// <summary> /// Returns a string representation of the endpoint. /// </summary> public override string? ToString() => DisplayName ?? base.ToString(); }

Metadata
非常重要,是存放控制器还有Action的元数据,在应用程序启动的时候就将控制器和Action的关键信息给存入,例如路由、特性、HttpMethod等

RequestDelegate
用于将请求(HttpContext)交给资源(Action)执行

AddControllers

我们来看下
AddControllers()

AddMvcCore()
及相关联的源码

MvcServiceCollectionExtensions
文件中,
AddControllersCore
方法用于添加控制器的核心服务,它最主要的作用是主要作用就是扫描所有的有关程序集封装成ApplicationPart。

public static class MvcServiceCollectionExtensions
{
    /// <summary>
    /// Adds services for controllers to the specified. This method will not
    /// register services used for views or pages.
    /// </summary>
    ///<param name="services">The <see cref="IServiceCollection" /> to add services to.</param>
    /// <returns>An <see cref="IMvcBuilder"/> that can be used to further configure the MVC services.</returns>
    /// <remarks>
    /// <para>
    /// This method configures the MVC services for the commonly used features with controllers for an API. This
    /// combines the effects of <see cref="MvcCoreServiceCollectionExtensions.AddMvcCore(IServiceCollection)"/>,
    /// <see cref="MvcApiExplorerMvcCoreBuilderExtensions.AddApiExplorer(IMvcCoreBuilder)"/>,
    /// <see cref="MvcCoreMvcCoreBuilderExtensions.AddAuthorization(IMvcCoreBuilder)"/>,
    /// <see cref="MvcCorsMvcCoreBuilderExtensions.AddCors(IMvcCoreBuilder)"/>,
    /// <see cref="MvcDataAnnotationsMvcCoreBuilderExtensions.AddDataAnnotations(IMvcCoreBuilder)"/>,
    /// and <see cref="MvcCoreMvcCoreBuilderExtensions.AddFormatterMappings(IMvcCoreBuilder)"/>.
/// </para> /// <para> /// To add services for controllers with views call <see cref="AddControllersWithViews(IServiceCollection)"/> /// on the resulting builder.
/// </para> /// <para> /// To add services for pages call <see cref="AddRazorPages(IServiceCollection)"/> /// on the resulting builder. /// on the resulting builder. /// </remarks> public static IMvcBuilder AddControllers(this IServiceCollection services) { if (services == null) { throw new ArgumentNullException(nameof(services)); } //添加Controllers核心服务 var builder = AddControllersCore(services); return new MvcBuilder(builder.Services, builder.PartManager); } private static IMvcCoreBuilder AddControllersCore(IServiceCollection services) { // This method excludes all of the view-related services by default. var builder = services .AddMvcCore()//这个是核心,返回IMvcCoreBuilder对象,其后的服务引入都是基于它的 .AddApiExplorer() .AddAuthorization() .AddCors() .AddDataAnnotations() .AddFormatterMappings(); if (MetadataUpdater.IsSupported) { services.TryAddEnumerable( ServiceDescriptor.Singleton<IActionDescriptorChangeProvider, HotReloadService>()); } return builder; } }

AddMvcCore
方法用于添加MVC的核心服务,下面的GetApplicationPartManager方法先获取ApplicationPartManager对象,然后将当前程序集封装成了ApplicationPart放进ApplicationParts集合中。
ConfigureDefaultFeatureProviders(partManager)
主要作用是创建了一个新的ControllerFeatureProvider实例放进了partManager的FeatureProviders属性中,注意这个ControllerFeatureProvider对象在后面遍历ApplicationPart的时候负责找出里面的Controller。
AddMvcCore()
方法其后是添加Routing服务再接着添加Mvc核心服务然后构建一个MvcCoreBuilder实例并返回


///<summary>
/// Extension methods for setting up essential MVC services in an.
///</summary>
public static class MvcCoreServiceCollectionExtensions
{
    ///<summary>
    /// Adds the minimum essential MVC services to the specified 
    /// <see cref="IServiceCollection" />. Additional services
    /// including MVC's support for authorization, formatters, and validation must be added separately 
    /// using the <see cref="IMvcCoreBuilder"/> returned from this method.
    ///</summary>
    ///<param name="services">The <see cref="IServiceCollection" /> to add services to.</param>
    /// <returns>
    /// An <see cref="IMvcCoreBuilder"/> that can be used to further configure the MVC services.
    /// </returns>
    /// <remarks>
    /// The <see cref="MvcCoreServiceCollectionExtensions.AddMvcCore(IServiceCollection)"/> 
    /// approach for configuring
    /// MVC is provided for experienced MVC developers who wish to have full control over the 
    /// set of default services
    /// registered. <see cref="MvcCoreServiceCollectionExtensions.AddMvcCore(IServiceCollection)"/> 
    /// will register
    /// the minimum set of services necessary to route requests and invoke controllers. 
    /// It is not expected that any
    /// application will satisfy its requirements with just a call to
    /// <see cref="MvcCoreServiceCollectionExtensions.AddMvcCore(IServiceCollection)"/>
    /// . Additional configuration using the
    /// <see cref="IMvcCoreBuilder"/> will be required.
    /// </remarks>
    public static IMvcCoreBuilder AddMvcCore(this IServiceCollection services)
    {
        if (services == null)
        {
            throw new ArgumentNullException(nameof(services));
        }
        //获取注入的IWebHostEnvironment环境对象
        var environment = GetServiceFromCollection(services);
        //获取程序中所有关联的程序集的ApplicationPartManager
        var partManager = GetApplicationPartManager(services, environment);
        services.TryAddSingleton(partManager);
        //给ApplicationPartManager添加ControllerFeature
        ConfigureDefaultFeatureProviders(partManager);
        //调用services.AddRouting();
        ConfigureDefaultServices(services);
        //添加MVC相关联的服务至IOC容器中
        AddMvcCoreServices(services);
        var builder = new MvcCoreBuilder(services, partManager);
        return builder;

    }

    private static ApplicationPartManager GetApplicationPartManager(IServiceCollection services, IWebHostEnvironment? environment)
    {
        var manager = GetServiceFromCollection(services);
        if (manager == null)
        {
            manager = new ApplicationPartManager();
            //获取当前主程序集的名称
            var entryAssemblyName = environment?.ApplicationName;
            if (string.IsNullOrEmpty(entryAssemblyName))
            {
                return manager;
            }
            //找出所有引用的程序集并将他们添加到ApplicationParts中
            manager.PopulateDefaultParts(entryAssemblyName);
        }

        return manager;
    }

    private static void ConfigureDefaultFeatureProviders(ApplicationPartManager manager)
    {
        if (!manager.FeatureProviders.OfType().Any())
        {
            manager.FeatureProviders.Add(new ControllerFeatureProvider());
        }
    }

    private static void ConfigureDefaultServices(IServiceCollection services)
    {
        services.AddRouting();
    }

    internal static void AddMvcCoreServices(IServiceCollection services)
    {
        //
        // Options
        //
        services.TryAddEnumerable(
            ServiceDescriptor.Transient<IConfigureOptions, MvcCoreMvcOptionsSetup>());
        services.TryAddEnumerable(
            ServiceDescriptor.Transient<IPostConfigureOptions, MvcCoreMvcOptionsSetup>());
        services.TryAddEnumerable(
            ServiceDescriptor.Transient<IConfigureOptions, ApiBehaviorOptionsSetup>());
        services.TryAddEnumerable(
            ServiceDescriptor.Transient<IConfigureOptions, MvcCoreRouteOptionsSetup>());

        //
        // Action Discovery
        //
        // These are consumed only when creating action descriptors, then they can be deallocated
        services.TryAddSingleton();
        services.TryAddEnumerable(
            ServiceDescriptor.Transient<IApplicationModelProvider, DefaultApplicationModelProvider>());
        services.TryAddEnumerable(
            ServiceDescriptor.Transient<IApplicationModelProvider, ApiBehaviorApplicationModelProvider>());
        services.TryAddEnumerable(
            ServiceDescriptor.Transient<IActionDescriptorProvider, ControllerActionDescriptorProvider>());

        services.TryAddSingleton<IActionDescriptorCollectionProvider, DefaultActionDescriptorCollectionProvider>();

        //
        // Action Selection
        //
        services.TryAddSingleton<IActionSelector, ActionSelector>();
        services.TryAddSingleton();

        // Will be cached by the DefaultActionSelector
        services.TryAddEnumerable(ServiceDescriptor.Transient<IActionConstraintProvider, DefaultActionConstraintProvider>());

        // Policies for Endpoints
        services.TryAddEnumerable(ServiceDescriptor.Singleton<MatcherPolicy, ActionConstraintMatcherPolicy>());

        //
        // Controller Factory
        //
        // This has a cache, so it needs to be a singleton
        services.TryAddSingleton<IControllerFactory, DefaultControllerFactory>();

        // Will be cached by the DefaultControllerFactory
        services.TryAddTransient<IControllerActivator, DefaultControllerActivator>();

        services.TryAddSingleton<IControllerFactoryProvider, ControllerFactoryProvider>();
        services.TryAddSingleton<IControllerActivatorProvider, ControllerActivatorProvider>();
        services.TryAddEnumerable(
            ServiceDescriptor.Transient<IControllerPropertyActivator, DefaultControllerPropertyActivator>());

        //
        // Action Invoker
        //
        // The IActionInvokerFactory is cachable
        services.TryAddSingleton<IActionInvokerFactory, ActionInvokerFactory>();
        services.TryAddEnumerable(
            ServiceDescriptor.Transient<IActionInvokerProvider, ControllerActionInvokerProvider>());

        // These are stateless
        services.TryAddSingleton();
        services.TryAddEnumerable(
            ServiceDescriptor.Singleton<IFilterProvider, DefaultFilterProvider>());
        services.TryAddSingleton<IActionResultTypeMapper, ActionResultTypeMapper>();

        //
        // Request body limit filters
        //
        services.TryAddTransient();
        services.TryAddTransient();
        services.TryAddTransient();

        //
        // ModelBinding, Validation
        //
        // The DefaultModelMetadataProvider does significant caching and should be a singleton.
        services.TryAddSingleton<IModelMetadataProvider, DefaultModelMetadataProvider>();
        services.TryAdd(ServiceDescriptor.Transient(s =>
        {
            var options = s.GetRequiredService<IOptions>().Value;
            return new DefaultCompositeMetadataDetailsProvider(options.ModelMetadataDetailsProviders);
        }));
        services.TryAddSingleton<IModelBinderFactory, ModelBinderFactory>();
        services.TryAddSingleton(s =>
        {
            var options = s.GetRequiredService<IOptions>().Value;
            var metadataProvider = s.GetRequiredService();
            return new DefaultObjectValidator(metadataProvider, options.ModelValidatorProviders, options);
        });
        services.TryAddSingleton();
        services.TryAddSingleton();

        //
        // Random Infrastructure
        //
        services.TryAddSingleton<MvcMarkerService, MvcMarkerService>();
        services.TryAddSingleton<ITypeActivatorCache, TypeActivatorCache>();
        services.TryAddSingleton<IUrlHelperFactory, UrlHelperFactory>();
        services.TryAddSingleton<IHttpRequestStreamReaderFactory, MemoryPoolHttpRequestStreamReaderFactory>();
        services.TryAddSingleton<IHttpResponseStreamWriterFactory, MemoryPoolHttpResponseStreamWriterFactory>();
        services.TryAddSingleton(ArrayPool.Shared);
        services.TryAddSingleton(ArrayPool.Shared);
        services.TryAddSingleton<OutputFormatterSelector, DefaultOutputFormatterSelector>();
        services.TryAddSingleton<IActionResultExecutor, ObjectResultExecutor>();
        services.TryAddSingleton<IActionResultExecutor, PhysicalFileResultExecutor>();
        services.TryAddSingleton<IActionResultExecutor, VirtualFileResultExecutor>();
        services.TryAddSingleton<IActionResultExecutor, FileStreamResultExecutor>();
        services.TryAddSingleton<IActionResultExecutor, FileContentResultExecutor>();
        services.TryAddSingleton<IActionResultExecutor, RedirectResultExecutor>();
        services.TryAddSingleton<IActionResultExecutor, LocalRedirectResultExecutor>();
        services.TryAddSingleton<IActionResultExecutor, RedirectToActionResultExecutor>();
        services.TryAddSingleton<IActionResultExecutor, RedirectToRouteResultExecutor>();
        services.TryAddSingleton<IActionResultExecutor, RedirectToPageResultExecutor>();
        services.TryAddSingleton<IActionResultExecutor, ContentResultExecutor>();
        services.TryAddSingleton<IActionResultExecutor, SystemTextJsonResultExecutor>();
        services.TryAddSingleton<IClientErrorFactory, ProblemDetailsClientErrorFactory>();
        services.TryAddSingleton<ProblemDetailsFactory, DefaultProblemDetailsFactory>();

        //
        // Route Handlers
        //
        services.TryAddSingleton(); // Only one per app
        services.TryAddTransient(); // Many per app

        //
        // Endpoint Routing / Endpoints
        //
        services.TryAddSingleton();
        services.TryAddSingleton();
        services.TryAddSingleton();
        services.TryAddSingleton();
        services.TryAddSingleton();
        services.TryAddEnumerable(ServiceDescriptor.Singleton<MatcherPolicy, DynamicControllerEndpointMatcherPolicy>());
        services.TryAddEnumerable(ServiceDescriptor.Singleton<IRequestDelegateFactory, ControllerRequestDelegateFactory>());

        //
        // Middleware pipeline filter related
        //
        services.TryAddSingleton();
        // This maintains a cache of middleware pipelines, so it needs to be a singleton
        services.TryAddSingleton();
        // Sets ApplicationBuilder on MiddlewareFilterBuilder
        services.TryAddEnumerable(ServiceDescriptor.Singleton<IStartupFilter, MiddlewareFilterBuilderStartupFilter>());
    }
}

下面的
PopulateDefaultParts()
方法从当前程序集找到所有引用到了的程序集(包括[assembly:ApplicationPart(“demo”)]中标记的)把他们封装成ApplciationPart,然后把他们放在了ApplciationPartManager的ApplicationParts属性中,用于后面筛选Controller提供数据基础。


namespace Microsoft.AspNetCore.Mvc.ApplicationParts
{
    /// 
    /// Manages the parts and features of an MVC application.
    /// 
    public class ApplicationPartManager
    {

        /// 
        /// Gets the list of  instances.
        /// 
        /// Instances in this collection are stored in precedence order. An  that appears
        /// earlier in the list has a higher precedence.
        /// An  may choose to use this an interface as a way to resolve conflicts when
        /// multiple  instances resolve equivalent feature values.
        /// 
        /// 
        public IList ApplicationParts { get; } = new List();

        internal void PopulateDefaultParts(string entryAssemblyName)
        {
            //获取相关联的程序集
            var assemblies = GetApplicationPartAssemblies(entryAssemblyName);

            var seenAssemblies = new HashSet();

            foreach (var assembly in assemblies)
            {
                if (!seenAssemblies.Add(assembly))
                {
                    // "assemblies" may contain duplicate values, but we want unique ApplicationPart instances.
                    // Note that we prefer using a HashSet over Distinct since the latter isn't
                    // guaranteed to preserve the original ordering.
                    continue;
                }

                var partFactory = ApplicationPartFactory.GetApplicationPartFactory(assembly);
                foreach (var applicationPart in partFactory.GetApplicationParts(assembly))
                {
                    ApplicationParts.Add(applicationPart);
                }
            }
        }

        private static IEnumerable GetApplicationPartAssemblies(string entryAssemblyName)
        {
            //加载当前主程序集
            var entryAssembly = Assembly.Load(new AssemblyName(entryAssemblyName));

            // Use ApplicationPartAttribute to get the closure of direct or transitive dependencies
            // that reference MVC.
            var assembliesFromAttributes = entryAssembly.GetCustomAttributes()
                .Select(name => Assembly.Load(name.AssemblyName))
                .OrderBy(assembly => assembly.FullName, StringComparer.Ordinal)
                .SelectMany(GetAssemblyClosure);

            // The SDK will not include the entry assembly as an application part. We'll explicitly list it
            // and have it appear before all other assemblies \ ApplicationParts.
            return GetAssemblyClosure(entryAssembly)
                .Concat(assembliesFromAttributes);
        }

        private static IEnumerable GetAssemblyClosure(Assembly assembly)
        {
            yield return assembly;

            var relatedAssemblies = RelatedAssemblyAttribute.GetRelatedAssemblies(assembly, throwOnError: false)
                .OrderBy(assembly => assembly.FullName, StringComparer.Ordinal);

            foreach (var relatedAssembly in relatedAssemblies)
            {
                yield return relatedAssembly;
            }
        }
    }
}

MapControllers

我们接下来看下Controller里的Action是怎样注册到路由模块的。MapControllers()方法执行时就会遍历遍历已经收集到的ApplicationPart进而将其中Controller里面的
Action
方法转换封装成一个个的EndPoint放到路由中间件的配置对象RouteOptions中然后交给Routing模块处理。还有一个重要作用是将EndpointMiddleware中间件注册到http管道中。EndpointMiddleware的一大核心代码主要是执行
Endpoint

RequestDelegate
委托,也即对
Controller
中的
Action
的执行。所有的Http请求都会走到EndpointMiddleware中间件中,然后去执行对应的Action。在应用程序启动的时候会把我们的所有的路由信息添加到一个EndpointSource的集合中去的,所以在MapController方法,其实就是在构建我们所有的路由请求的一个RequestDelegate,然后在每次请求的时候,在EndpointMiddleWare中间件去执行这个RequestDelegate,从而走到我们的接口中去。简而言之,这个方法就是将我们的所有路由信息添加到一个EndpointDataSource的抽象类的实现类中去,默认是ControllerActionEndpointDataSource这个类,在这个类中有一个基类ActionEndpointDataSourceBase,ControllerActionEndpointDataSource初始化的时候会订阅所有的Endpoint的集合的变化,每变化一次会向EndpointSource集合添加Endpoint,从而在请求的时候可以找到这个终结点去调用。

我们来看下
MapControllers()
的源码

public static class ControllerEndpointRouteBuilderExtensions
{
    ///
    /// Adds endpoints for controller actions to the without specifying any routes.
    ///
    ///The .
    /// An  for endpoints associated with controller actions.
    public static ControllerActionEndpointConventionBuilder MapControllers(this IEndpointRouteBuilder endpoints)
    {
        if (endpoints == null)
        {
            throw new ArgumentNullException(nameof(endpoints));
        }

        EnsureControllerServices(endpoints);

        return GetOrCreateDataSource(endpoints).DefaultBuilder;
    }

    private static void EnsureControllerServices(IEndpointRouteBuilder endpoints)
    {
        var marker = endpoints.ServiceProvider.GetService();
        if (marker == null)
        {
            throw new InvalidOperationException(Resources.FormatUnableToFindServices(
                nameof(IServiceCollection),
                "AddControllers",
                "ConfigureServices(...)"));
        }
    }

    private static ControllerActionEndpointDataSource GetOrCreateDataSource(IEndpointRouteBuilder endpoints)
    {
        var dataSource = endpoints.DataSources.OfType().FirstOrDefault();
        if (dataSource == null)
        {
            var orderProvider = endpoints.ServiceProvider.GetRequiredService();
            var factory = endpoints.ServiceProvider.GetRequiredService();
            dataSource = factory.Create(orderProvider.GetOrCreateOrderedEndpointsSequenceProvider(endpoints));
            endpoints.DataSources.Add(dataSource);
        }

        return dataSource;
    }
}

首先
EnsureControllerServices
方法检查mvc服务是否注入了,
GetOrCreateDataSource
方法执行完就获取到了dateSource,dateSource中就是所有的Action信息。需要注意的是
ControllerActionEndpointDataSource
这个类,它里面的方法帮我们创建路由终结点。我们来看一下它的定义:

internal class ControllerActionEndpointDataSource : ActionEndpointDataSourceBase
{
    private readonly ActionEndpointFactory _endpointFactory;
    private readonly OrderedEndpointsSequenceProvider _orderSequence;
    private readonly List _routes;

    public ControllerActionEndpointDataSource(
        ControllerActionEndpointDataSourceIdProvider dataSourceIdProvider,
        IActionDescriptorCollectionProvider actions,
        ActionEndpointFactory endpointFactory,
        OrderedEndpointsSequenceProvider orderSequence)
        : base(actions)
    {
        _endpointFactory = endpointFactory;

        DataSourceId = dataSourceIdProvider.CreateId();
        _orderSequence = orderSequence;

        _routes = new List();

        DefaultBuilder = new ControllerActionEndpointConventionBuilder(Lock, Conventions);

        // IMPORTANT: this needs to be the last thing we do in the constructor.
        // Change notifications can happen immediately!
        Subscribe();
    }

    public int DataSourceId { get; }

    public ControllerActionEndpointConventionBuilder DefaultBuilder { get; }

    // Used to control whether we create 'inert' (non-routable) endpoints for use in dynamic
    // selection. Set to true by builder methods that do dynamic/fallback selection.
    public bool CreateInertEndpoints { get; set; }

    public ControllerActionEndpointConventionBuilder AddRoute(
        string routeName,
        string pattern,
        RouteValueDictionary? defaults,
        IDictionary<string, object?>? constraints,
        RouteValueDictionary? dataTokens)
    {
        lock (Lock)
        {
            var conventions = new List<Action>();
            _routes.Add(new ConventionalRouteEntry(routeName, pattern, defaults, constraints, dataTokens, _orderSequence.GetNext(), conventions));
            return new ControllerActionEndpointConventionBuilder(Lock, conventions);
        }
    }

    protected override List CreateEndpoints(IReadOnlyList actions, IReadOnlyList<Action> conventions)
    {
        var endpoints = new List();
        var keys = new HashSet(StringComparer.OrdinalIgnoreCase);

        // MVC guarantees that when two of it's endpoints have the same route name they are equivalent.
        //
        // However, Endpoint Routing requires Endpoint Names to be unique.
        var routeNames = new HashSet(StringComparer.OrdinalIgnoreCase);

        // For each controller action - add the relevant endpoints.
        //
        // 1. If the action is attribute routed, we use that information verbatim
        // 2. If the action is conventional routed
        //      a. Create a *matching only* endpoint for each action X route (if possible)
        //      b. Ignore link generation for now
        for (var i = 0; i < actions.Count; i++)
        {
            if (actions[i] is ControllerActionDescriptor action)
            {
                _endpointFactory.AddEndpoints(endpoints, routeNames, action, _routes, conventions, CreateInertEndpoints);

                if (_routes.Count > 0)
                {
                    // If we have conventional routes, keep track of the keys so we can create
                    // the link generation routes later.
                    foreach (var kvp in action.RouteValues)
                    {
                        keys.Add(kvp.Key);
                    }
                }
            }
        }

        // Now create a *link generation only* endpoint for each route. This gives us a very
        // compatible experience to previous versions.
        for (var i = 0; i < _routes.Count; i++)
        {
            var route = _routes[i];
            _endpointFactory.AddConventionalLinkGenerationRoute(endpoints, routeNames, keys, route, conventions);
        }

        return endpoints;
    }

    internal void AddDynamicControllerEndpoint(IEndpointRouteBuilder endpoints, string pattern, Type transformerType, object? state, int? order = null)
    {
        CreateInertEndpoints = true;
        lock (Lock)
        {
            order ??= _orderSequence.GetNext();

            endpoints.Map(
                pattern,
                context =>
                {
                    throw new InvalidOperationException("This endpoint is not expected to be executed directly.");
                })
                .Add(b =>
                {
                    ((RouteEndpointBuilder)b).Order = order.Value;
                    b.Metadata.Add(new DynamicControllerRouteValueTransformerMetadata(transformerType, state));
                    b.Metadata.Add(new ControllerEndpointDataSourceIdMetadata(DataSourceId));
                });
        }
    }
}


CreateEndpoints
方法中会遍历每个
ActionDescriptor
对象,
ActionDescriptor
对象里面存储的是Action方法的元数据。然后创建一个个的Endpoint实例,Endpoint对象里面有一个RequestDelegate参数,当请求进入的时候会执行这个委托进入对应的Action。另外这其中还有一个DefaultBuilder属性,可以看到他返回的是
ControllerActionEndpointConventionBuilder
对象,这个对象是用来构建约定路由的。
AddRoute
方法也是用来添加约定路由的。我们再来看下构造函数中的
Subscribe()
方法,这个方法是调用父类
ActionEndpointDataSourceBase
中的。我们来看一下这个类:

internal abstract class ActionEndpointDataSourceBase : EndpointDataSource, IDisposable
{
    private readonly IActionDescriptorCollectionProvider _actions;

    // The following are protected by this lock for WRITES only. This pattern is similar
    // to DefaultActionDescriptorChangeProvider - see comments there for details on
    // all of the threading behaviors.
    protected readonly object Lock = new object();

    // Protected for READS and WRITES.
    protected readonly List<Action> Conventions;

    private List? _endpoints;
    private CancellationTokenSource? _cancellationTokenSource;
    private IChangeToken? _changeToken;
    private IDisposable? _disposable;

    public ActionEndpointDataSourceBase(IActionDescriptorCollectionProvider actions)
    {
        _actions = actions;

        Conventions = new List<Action>();
    }

    public override IReadOnlyList Endpoints
    {
        get
        {
            Initialize();
            Debug.Assert(_changeToken != null);
            Debug.Assert(_endpoints != null);
            return _endpoints;
        }
    }

    // Will be called with the lock.
    protected abstract List CreateEndpoints(IReadOnlyList actions, IReadOnlyList<Action> conventions

    protected void Subscribe()
    {
        // IMPORTANT: this needs to be called by the derived class to avoid the fragile base class
        // problem. We can't call this in the base-class constuctor because it's too early.
        //
        // It's possible for someone to override the collection provider without providing
        // change notifications. If that's the case we won't process changes.
        if (_actions is ActionDescriptorCollectionProvider collectionProviderWithChangeToken)
        {
            _disposable = ChangeToken.OnChange(
                () => collectionProviderWithChangeToken.GetChangeToken(),
                UpdateEndpoints);
        }
    }

    public override IChangeToken GetChangeToken()
    {
        Initialize();
        Debug.Assert(_changeToken != null);
        Debug.Assert(_endpoints != null);
        return _changeToken;
    }

    public void Dispose()
    {
        // Once disposed we won't process updates anymore, but we still allow access to the endpoints.
        _disposable?.Dispose();
        _disposable = null;
    }

    private void Initialize()
    {
        if (_endpoints == null)
        {
            lock (Lock)
            {
                if (_endpoints == null)
                {
                    UpdateEndpoints();
                }
            }
        }
    }

    private void UpdateEndpoints()
    {
        lock (Lock)
        {
            var endpoints = CreateEndpoints(_actions.ActionDescriptors.Items, Conventions);

            // See comments in DefaultActionDescriptorCollectionProvider. These steps are done
            // in a specific order to ensure callers always see a consistent state.

            // Step 1 - capture old token
            var oldCancellationTokenSource = _cancellationTokenSource;

            // Step 2 - update endpoints
            _endpoints = endpoints;

            // Step 3 - create new change token
            _cancellationTokenSource = new CancellationTokenSource();
            _changeToken = new CancellationChangeToken(_cancellationTokenSource.Token);

            // Step 4 - trigger old token
            oldCancellationTokenSource?.Cancel();
        }
    }
}

_actions
属性是注入进来的,这个对象是我们在
services.AddMvcCore()
中注入进来的:
services.TryAddSingleton<IActionDescriptorCollectionProvider, DefaultActionDescriptorCollectionProvider>();
我们来说下
ChangeToken.OnChange()
方法,他里面有两个委托类型的参数,
GetChangeToken()
它的作用是用来感知
ActionDescriptor
数据源的变化,然后执行
UpdateEndpoints
方法中的具体的逻辑:

  • 首先更新ActionDescriptors对象的具体元数据信息
  • 获取旧的令牌
  • 更新终结点
  • 创建新的令牌
  • 废弃旧的令牌


大家做的项目都有鉴权、授权的功能。而每一个角色可以访问的资源是不相同的,因此策略鉴权是非常关键的一步,它可以阻止非此菜单资源的角色用户访问此菜单的接口。一般来说有一个接口表(Module)、一个菜单表(Permission)、一个接口菜单关系表(ModulePermission),接口需要挂在菜单下面,假如一个项目几百个接口,那录起来可就麻烦了。按照我们上面说的,在管道构建时,程序就会扫描所有相关程序集中Controller的Action然后交给“路由”模块去管理。Action的这些元数据信息会存在我们上面说的IActionDescriptorCollectionProvider中的ActionDescriptorCollection对象的ActionDescriptor集合中,这样在http请求到来时“路由”模块才能寻找到正确的Endpoint,进而找到Action并调用执行。那么我们就可以读到项目中所有注册的路由,然后导入到数据库表中

软件测试作业,用ChatGPT4来帮个小忙,
小划水,勿喷勿喷,近期有相关作业的同学看到我的文章,建议修改一下,别撞车了
,哈哈哈~

提出需求

个人输入需求: Java实现键盘输入日期 输出前一天,需要考虑润年和非润年,2月是否有29号,大月小月的区分等细节处理,不符合的有对应提示,不使用java包里的封装好的类

ChatGPT4分析出的语义:实现了输入日期输出前一天,并且考虑了闰年和非闰年、2月的天数、大月和小月等细节处理。如果输入的日期格式不正确,会有对应的提示。

代码实现

import java.util.Scanner;

public class TheDayBefore {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.print("请输入一个日期(格式:yyyy-MM-dd):");
        String inputString = scanner.nextLine();
        String[] inputArray = inputString.split("-");
        int year, month, day;
        if (inputArray.length == 3) {
            try {
                year = Integer.parseInt(inputArray[0]);
                month = Integer.parseInt(inputArray[1]);
                day = Integer.parseInt(inputArray[2]);
            } catch (NumberFormatException e) {
                System.out.println("日期格式不正确!");
                return;
            }
            if (month < 1 || month > 12) {
                System.out.println("月份不正确!");
                return;
            }
            if (day < 1 || day > 31) {
                System.out.println("日期不正确!");
                return;
            }
            if ((day == 31 && (month == 4 || month == 6 || month == 9 || month == 11)) ||
                    (day >= 30 && month == 2) ||
                    (day == 29 && month == 2 && !isLeapYear(year)) ||
                    (year < 0)) {
                System.out.println("日期不正确!");
                return;
            }
            int prevYear = year;
            int prevMonth = month;
            int prevDay = day - 1;
            if (prevDay == 0) {
                prevMonth--;
                if (prevMonth == 0) {
                    prevYear--;
                    prevMonth = 12;
                }
                if (prevMonth == 2) {
                    if (isLeapYear(prevYear)) {
                        prevDay = 29;
                    } else {
                        prevDay = 28;
                    }
                } else {
                    if (prevMonth == 4 || prevMonth == 6 || prevMonth == 9 || prevMonth == 11) {
                        prevDay = 30;
                    } else {
                        prevDay = 31;
                    }
                }
            }
            System.out.println("前一天是:" + prevYear + "-" + formatMonth(prevMonth) + "-" + formatDay(prevDay));
        } else {
            System.out.println("日期格式不正确!");
        }
    }

    private static boolean isLeapYear(int year) {
        return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
    }

    private static String formatMonth(int month) {
        if (month < 10) {
            return "0" + month;
        } else {
            return String.valueOf(month);
        }
    }

    private static String formatDay(int day) {
        if (day < 10) {
            return "0" + day;
        } else {
            return String.valueOf(day);
        }
    }

}

需求分析

过于划水,不太行,还是得简单分析一下需求叭,哈哈哈~

  1. 首先将目标定义为java,从年开始划分,判断输入的年是否为闰年:为闰年则对于二月这个特殊月,闰年为29天,平年为28天。例如输入2023-02-28是正确的,因为2023年为平年。
  2. 输入的月份月大还是月小,若是月大则除去二月以外,月大有31天,月小有30天。例如输入2023-04-01,前一天month=month-1,day=31。
  3. 输入的月是否为三月,若是三月例如2023-03-01(平年)前一天month = month-1;润年类似。
  4. 输入的月份是否是1月,例如2023-01-01则前一天为2022-12-31
    及year = year - 1,month = 12,day = 31。

单元测试

1.测试输入的月份月大还是月小。

2.判断输入的年是否为闰年和输入的月是否为三月。

3.输入的月份是否是1月。

等价类划分

M1={月份:4、6、9、11}

M2={月份:1、3、5、7、8、10、12}

M3={月份:2}

D1={日期:1}

D2={日期:2<=日期<=27}

D3={日期:28}

D4={日期:29}

D5={日期:30}

D6={日期:31}

Y1={年:是闰年}

Y2={年:不是闰年}

决策表

1 2 3 4 5 6 7 8 9 10 11
月份在 M2 M3 M3 M2 M1 M1 M3 M2 M1 M4 M4
日期在 D1 D4 D3 D1 D1 D1 D1 D2-D6 D2-D5 D1 D1
年份在 Y1 Y2 Y1 Y2
year-1
moth-1
day-1
year复位12
moth复位12
day复位31
day复位30
day复位28
day复位29

  1. 使用混合高斯模型 GMM,计算如下数据点的聚类过程:
    \(Data = np.array([1,2,6,7])\)
    均值初值为:
    \(\mu_1, \mu_2 = 1, 5\)
    权重初值为:
    \(w_1, w_2 = 0.5, 0.5\)
    方差:
    \(std_1, std_2 = 1, 1\)
    \(K = 2\)
    10 次迭代后数据的聚类标签是多少?

采用python代码实现:

from scipy import stats
import numpy as np

#初始化数据
Data = np.array([1,2,6,7])
w1 , w2 = 0.5, 0.5
mu1 , mu2 = 1, 5
std1 , std2 = 1, 1

n = len(Data) # 样本长度
zij=np.zeros([n,2])
for t in range(10):
    # E-step 依据当前参数,计算每个数据点属于每个子分布的概率
    z1_up = w1 * stats.norm(mu1 ,std1).pdf(Data)
    z2_up = w2*stats.norm(mu2 , std2).pdf(Data)
    z_all = (w1*stats.norm(mu1 ,std1).pdf(Data)+w2*stats.norm(mu2 ,std2).pdf(Data))+0.001
    rz1 = z1_up/z_all # 为甲分布的概率
    rz2 = z2_up/z_all # 为乙分布的概率
    # M-step 依据 E-step 的结果,更新每个子分布的参数。
    mu1 = np.sum(rz1*Data)/np.sum(rz1)
    mu2 = np.sum(rz2*Data)/np.sum(rz2)
    std1 = np.sum(rz1*np.square(Data-mu1))/np.sum(rz1)
    std2 = np.sum(rz2*np.square(Data-mu2))/np.sum(rz2)
    w1 = np.sum(rz1)/n
    w2 = np.sum(rz2)/n
for i in range(n):
    zij[i][0] = rz1[i]/(rz1[i]+rz2[i])
    zij[i][1] = rz2[i]/(rz1[i]+rz2[i])

labels = np.argmax(zij, axis=1)#输出每一行的最大值,0或1  axis表示返回每一行中最大值所在列的索引
print(labels)

聚类标签输出结果:
[0 0 1 1]

也就是说,10 次迭代后数据的
聚类标签
是1,2归为
0
类6,7归为
1

附注:

如果 axis 为 None,那么 np.argmax 会将数组展平为一维,然后返回最大值的索引。例如:

>>> a = np.array([[1, 2], [3, 4]])
>>> np.argmax(a)
3

如果 axis 为 0,那么 np.argmax 会沿着第一个维度(行)进行最大值的查找,返回每一列中最大值所在的行索引。例如:

>>> a = np.array([[1, 2], [3, 4]])
>>> np.argmax(a, axis=0)
array([1, 1])

如果 axis 为 1,那么 np.argmax 会沿着第二个维度(列)进行最大值的查找,返回每一行中最大值所在的列索引。例如:

>>> a = np.array([[1, 2], [3, 4]])
>>> np.argmax(a, axis=1)
array([1, 1])

在之前问题中,np.argmax([gamma1, gamma2], axis=0) 的意思是沿着第一个维度(gamma1 和 gamma2)进行最大值的查找,返回每个数据点属于哪个子分布的概率更大。

  1. 假设我们的数据集有 10 个 3 维数据, 需要用 PCA 降到 2 维特征。

    array([
        [ 3.25, 1.85, -1.29],
        [ 3.06, 1.25, -0.18],
        [ 3.46, 2.68, 0.64],
        [ 0.3 , -0.1 , -0.79],
        [ 0.83, -0.21, -0.88],
        [ 1.82, 0.99, 0.16],
        [ 2.78, 1.75, 0.51],
        [ 2.08, 1.5 , -1.06],
        [ 2.62, 1.23, 0.04],
        [ 0.83, -0.69, -0.61]])
    

    给出求解过程

解:

  1. 对所有的样本进行中心化:

\[x^{(i)}=x^{(i)}-\frac{1}{m} \sum_{j=1}^{m} x^{(j)}
\]

得到:

X=np.array([
     [ 1.147  0.825 -0.944]
     [ 0.957  0.225  0.166]
     [ 1.357  1.655  0.986]
     [-1.803 -1.125 -0.444]
     [-1.273 -1.235 -0.534]
     [-0.283 -0.035  0.506]
     [ 0.677  0.725  0.856]
     [-0.023  0.475 -0.714]
     [ 0.517  0.205  0.386]
     [-1.273 -1.715 -0.264]])
  1. 计算样本的协方差矩阵 $X X^{T} $
covM2=np.array([[1.26344556 1.08743889 0.32030889], 
[1.08743889 1.11076666666 0.31666666611],
[0.32030889 0.31666666611 0.45449333]])
  1. 对矩阵 $X X^{T} $ 进行特征值分解

取出最大的
\(\mathrm{n}^{\prime}\)
个特征值对应的特征向量 $ \left(w_{1}, \ldots, w_{n^{\prime}}\right) $, 将所有的特征向量标准化后,组成特征向量矩阵
\(W\)

3.1求出特征值:

eigval=np.array([2.38219729 0.09637041 0.35013229])

3.2特征向量标准化:

eigvec=np.array([
[ 0.71144     0.67380165 -0.19961077],
[ 0.66498574 -0.73733944 -0.11884665],
[ 0.22725997  0.04818606  0.97264126]])

3.3取出特征值最大的2个特征值索引,也就是
\([2.38, 0.35]\)
对应的第1列和第3列:

indexes=[2 0]

3.4特征向量矩阵W:(对
eigvec
取了第
3
列和第
1
列)

W=np.array([
[-0.19961077  0.71144   ], 
[-0.11884665   0.66498574], 
[ 0.97264126   0.22725997]])
  1. 对样本集中的每一个样本
    \(x^{(i)}\)
    , 转化为新的样本
    \(z^{(i)}=W^{T} x^{(i)}\)
    ,得到输出样本集 $D=\left(z^{(1)}, \ldots z^{(m)}\right) $

X:3×10 W:3×2
\(x\cdot W =10\times3 \quad 3\times2\)
因为输入行列转置,结果是一致的

D=np.array([
     [-1.24517539  1.15010151]
     [-0.05630956  0.86819503]
     [ 0.49146125  2.29005381]
     [ 0.06174799 -2.1317387 ]
     [-0.1185103  -1.84827733]
     [ 0.55280596 -0.10961848]
     [ 0.6112806   1.15829407]
     [-0.74632697  0.13724149]
     [ 0.24787719  0.5918589 ]
     [ 0.20114923 -2.10611029]])

代码:

import numpy as np

X=np.array([
    [ 3.25, 1.85, -1.29],
    [ 3.06, 1.25, -0.18],
    [ 3.46, 2.68, 0.64],
    [ 0.3 , -0.1 , -0.79],
    [ 0.83, -0.21, -0.88],
    [ 1.82, 0.99, 0.16],
    [ 2.78, 1.75, 0.51],
    [ 2.08, 1.5 , -1.06],
    [ 2.62, 1.23, 0.04],
    [ 0.83, -0.69, -0.61]])

def pca(X, d):
    # Centralization中心化
    means = np.mean(X, 0)
    X = X - means
    print(X)
    # Covariance Matrix 计算样本协方差矩阵
    M=len(X)
    X=np.mat(X)    
    covM2=np.cov(X.T)
    # 求出特征值,特征值分解
    eigval , eigvec = np.linalg.eig(covM2)
    indexes = np.argsort(eigval)[-d:]
    W = eigvec[:, indexes]
    return X*W
print(pca(X, 2))

附注:

np.cov()是一个用于计算协方差矩阵的函数,它可以接受一个或两个数组作为参数,返回一个二维数组,表示协方差矩阵。

协方差矩阵是一个对称矩阵,它的对角线元素表示各个变量的方差,非对角线元素表示两个变量之间的协方差。协方差反映了两个变量的线性相关程度,如果协方差为正,说明两个变量正相关;如果协方差为负,说明两个变量负相关;如果协方差为零,说明两个变量无相关性。

np.cov()的用法如下:

np.cov(m, y=None, rowvar=True, bias=False, ddof=None, fweights=None, aweights=None)

参数说明:

  • m: 一个一维或二维的数组,表示多个变量和观测值。如果是一维数组,表示一个变量的观测值;如果是二维数组,每一行表示一个变量,每一列表示一个观测值。
  • y: 可选参数,另一个一维或二维的数组,表示另一组变量和观测值,必须和m具有相同的形状。
  • rowvar: 可选参数,布尔值,默认为True。如果为True,表示每一行代表一个变量;如果为False,表示每一列代表一个变量。
  • bias: 可选参数,布尔值,默认为False。如果为False,表示计算无偏协方差(除以n-1);如果为True,表示计算有偏协方差(除以n)。
  • ddof: 可选参数,整数,默认为None。如果不为None,则覆盖由bias隐含的默认值。ddof=0表示计算有偏协方差;ddof=1表示计算无偏协方差。
  • fweights: 可选参数,一维数组或整数,默认为None。表示每次观测的频率权重。
  • aweights: 可选参数,一维数组,默认为None。表示每个变量的可靠性权重。

返回值:

  • 一个二维数组,表示协方差矩阵。

举例说明:

import numpy as np

# 生成两组随机数据
x = np.random.randn(10)
y = np.random.randn(10)

# 计算x和y的协方差矩阵
cov_xy = np.cov(x,y)
print(cov_xy)
# 输出:
[[ 0.8136679  -0.01594772]
 [-0.01594772  0.84955963]]

# 计算x和y的相关系数矩阵
corr_xy = np.corrcoef(x,y)
print(corr_xy)
# 输出:
[[ 1.         -0.01904402]
 [-0.01904402  1.        ]]