ASP.NET Core 6.0 如何处理丢失的 Startup.cs 文件

news2025/1/27 21:08:32

介绍

    .NET 6.0 已经发布,ASP.NET Core 6.0 也已发布。其中有不少变化让很多人感到困惑。例如,“谁动了我的奶酪”,它在哪里Startup.cs?在这篇文章中,我将深入研究这个问题,看看它移动到了哪里以及其他变化。

    ASP.NET Core 的中间件并没有发生根本性的变化,但部分项目结构以及注册依赖项的位置发生了变化。为了更好地理解它,最好从 .NET Core 3.1 项目模板开始,然后手动升级它,看看它与新模板相比如何。

升级旧式控制台项目

    首先,让我们创建一个新的控制台项目。我将其命名为OldToNew。我选择了 .NET Core 3.1 目标,并将其升级到 .NET 6.0 以查看差异。如果您已经使用 .NET 一段时间,您会在文件中认出这个项目结构Program.cs。 

using System;

namespace OldToNew
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
        }
    }
}

        在 .NET 6.0 中,这些变化旨在简化和消除应用程序中的冗余。他们引入的首批功能之一是所谓的Filescoped Namespaces。传统命名空间如下所示:

namespace OldToNew
{
    // code goes here.
}

        如果您已经使用过 .NET 一段时间,那么您可能从未在文件中放置过多个命名空间。您可以删除花括号,只需添加分号,将整个文件标记为使用一个命名空间。

namespace OldToNew;
// code goes here.
 

using System;

namespace OldToNew;

internal class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Hello World!");
    }
}
 

        Visual Studio 将会向我抱怨,因为这是一个 .NET Core 3.1 项目,所以在我们走得太远之前,我们需要编辑 .NET Core 3.1 .csproj 文件并将其转换为 .NET 6.0 应用程序。

<!-- .NET Core 3.1 -->
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>

</Project>

<!-- .NET 6.0 -->
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
  </PropertyGroup>

</Project>

        一旦我们做出这一改变,Visual Studio 就会对文件范围的命名空间感到满意。

        我们要做的下一个更改是删除该using System;行。为此,我们需要再次编辑项目文件并启用Implicit Usings。

<!-- .NET 6.0 -->
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>

</Project>

        如果我们启用隐式 using 语句,我们通常使用的大多数常用 using 语句将默认包含在 SDK 中,您不再需要将它们包含在文件中。我们可以删除该using System;行,因为编译器会自动为我们添加 using 语句。

namespace OldToNew;

internal class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Hello World!");
    }

        他们引入的下一个功能是所谓的“顶级语句”。 其目的是删除每个控制台应用程序或 ASP.NET Core 应用程序中存在的“垃圾”。

        使用顶级语句,我们可以删除static void Main(string[] args)方法和花括号以及命名空间和class Program声明。

Console.WriteLine("Hello World!");

        一旦删除所有这些,您就会看到我们剩下的唯一代码就是我们的Console.WriteLine()方法!

将控制台应用更改为 Web 应用 (ASP.NET Core)

    目前,这只是一个简单的控制台应用程序,但我想将其转换为 ASP.NET Core 应用程序。在执行此操作之前,让我们先查看解决方案资源管理器中的依赖项和框架节点。

        您可以在上方看到,Frameworks这Microsoft.NETCore.App是包含创建控制台应用程序所需的所有包的 SDK。让我们将 .csproj 文件中的 SDK 类型更改为 .Web 类型项目,看看会发生什么Microsoft.NET.Sdk:Microsoft.NET.Sdk.Web。

<!-- .NET 6.0 -->
<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
  </PropertyGroup>

</Project>
注意现在依赖下发生的情况:

        该框架现在包括Microsoft.AspNetCore.App将引入创建 ASP.NET Core 应用程序所需的所有包。它还将修改全局 using 语句以包含 ASP.NET 特定的 using 语句。

        现在让我们删除该Console.WriteLine("Hello World!");行并添加 ASP.NET Core 特定的代码。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.Run(); 

        我们现在正在构建一个 Web 应用程序。WebApplication对象从哪里来?它属于Microsoft.AspNetCore.Builder命名空间。

        我们可以运行这个应用程序,但是由于没有端点,所以会有点无聊。让我们添加一个端点来完成这个任务:

app.MapGet("/", () => {
    return "Hello World!";
});   

        我们刚刚在 ASP.NET Core 6.0 中创建了一个最小 API Web 应用程序。让我们运行它并看看会发生什么。 

        当我们启动默认路径时,它会返回,hello world因此我们有一个功能齐全的 Web 应用程序。最少的 API 是快速构建 Web API 项目的好方法。

Startup.cs 怎么样?

    ASP.NET Core 6.0 中的中间件流程与完整 Web API MVC 项目的流程类似,并且共享许多相同的实现。在以前的 ASP.NET Core 项目中,您将获得一个Startup.cs包含两个方法的类ConfigureServices()和Configure()。

    在 ConfigureServices()中,您注册了依赖项注入服务。在 Configure() 中,您概述了中间件管道顺序和结构。

    在我们的新项目中,这是什么样子的?您将依赖注入放在哪里?答案是在第一行和第二行之间,注册中间件在第二行和第三行之间,如下所示。

var builder = WebApplication.CreateBuilder(args);
// REGISTER SERVICES HERE
var app = builder.Build();
// REGISTER MIDDLEWARE HERE
app.Run();

例如,如果我想添加身份验证,我会像这样注册: 

var builder = WebApplication.CreateBuilder(args);
// REGISTER SERVICES HERE
builder.Services.AddAuthentication(...) ...
builder.Services.AddAuthorization();
var app = builder.Build();
// REGISTER MIDDLEWARE HERE
app.UseAuthentication();
app.UseAuthorization();
app.MapGet("/", () => {
    return "Hello World!";
});  
app.Run();

        中间件管道中的顺序很重要,因此我在中间件MapGet()上方添加了UseAuthentication()和UseAuthorization()。但是,为了使其生效,您需要使用属性注释要保护的端点。这将需要使用语句[Authorize],必须引用:Microsoft.AspNetCore.Authorization 。

using Microsoft.AspNetCore.Authorization;

...

app.MapGet("/",[Authorize]() => {
    return "Hello World!";
});

Program.cs到目前为止,我们的文件的完整代码如下所示: 

var builder = WebApplication.CreateBuilder(args);
// REGISTER SERVICES HERE
builder.Services.AddAuthentication();
builder.Services.AddAuthorization();
var app = builder.Build();
// REGISTER MIDDLEWARE HERE
app.UseAuthentication();
app.UseAuthorization();
app.MapGet("/",[Authorize] () => {
    return "Hello World!";
});  
app.Run(); 

        如果我们现在运行它,我们将会得到一个异常,因为我们从未指定身份验证方案。

        让我们修复它。我们可以选择多种不同类型的身份验证方案,但在 WebAPI 中,我们通常使用诸如 bearer token 或 JWT token 之类的东西来保护应用程序。

        让我们继续将 JWT 承载者添加到最小 API。我们需要Microsoft.AspNetCore.Authentication.JwtBearer通过 NuGet 导入包,然后添加以下代码:

using Microsoft.AspNetCore.Authentication.JwtBearer;

...

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer();

所以完整的块应该是这样的:

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;

var builder = WebApplication.CreateBuilder(args);
// REGISTER SERVICES HERE
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer();
builder.Services.AddAuthorization();
var app = builder.Build();
// REGISTER MIDDLEWARE HERE
app.UseAuthentication();
app.UseAuthorization();
app.MapGet("/", [Authorize] () => {
    return "Hello World!";
});
app.Run();

        JWT Bearer 已添加到应用程序中,让我们运行它并查看会发生什么。现在我们得到了我们正在寻找的状态,401 unauthorized因为我们没有为其提供任何类型的令牌。还需要其他步骤来设置获取令牌的方法并确保我们只允许接受有效的令牌,但这超出了本文的范围。本文的重点是演示在哪里注册依赖项和中间件以及如何使用它们。

        我们应该做的最后一项修改是将 [Authorize] 属性更改为使用“流畅的语法”,如下所示:

//FROM THIS:
app.MapGet("/", [Authorize] () => {
    return "Hello World!";
});

//TO THIS:
app.MapGet("/", () => {
  return "Hello World!";
}).RequireAuthorization();

它们都做同样的事情,但是在最小 API 情况下,“流畅的语法”感觉更自然。 

恢复丢失的 Startup.cs 文件

    接下来,让我们看看能否恢复我们的老朋友Startup.cs文件。如果将所有内容都放在一个文件中,最小 API 结构可能会变得混乱和臃肿。

    一定有办法让我们更好地组织这些。我将首先向Startup.cs项目添加一个名为的新类。

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {

    }

     public void Configure(WebApplication app, IWebHostEnvironment env)
    {

    }

        这看起来应该与过去的完整 Web API 项目模板很相似。我只是将其粘贴到我的新Startup.cs文件中并删除了命名空间。该Startup.cs文件需要IConfiguration传入一个对象。我们可以从我们的 中提供该对象吗Program.cs? 

//  Program.cs file
var builder = WebApplication.CreateBuilder(args);
var startup = new Startup(builder.Configuration); 

        是的。我们可以这样做。builder创建的对象包含一个Configuration属性,我们可以使用该属性将其传递给Startup构造函数。

        接下来,让我们尝试为该ConfigureServices()方法提供一个IServiceCollection。

//  Program.cs file
var builder = WebApplication.CreateBuilder(args);
var startup = new Startup(builder.Configuration);
startup.ConfigureServices(builder.Services); 

        同样,builder包含一个Services我们可以用来传递给ConfigureServices()方法的属性。现在我们可以从中删除所有依赖注入代码Program.cs并将其移动到ConfigureServices中的方法Startup.cs。 

// Startup.cs file
public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer();
    services.AddAuthorization();
}

接下来,让我们尝试Configure()WebApplication类提供方法。 

//  Program.cs file
var builder = WebApplication.CreateBuilder(args);
var startup = new Startup(builder.Configuration);
startup.ConfigureServices(builder.Services);

var app = builder.Build();

startup.Configure(app, builder.HostingEnvironment); 

        有趣的是,虽然我们传递的app是 类型WebApplication,但如果你检查它,该Configure()方法需要的是IApplicationBuilder,但它似乎没问题。为什么?如果你深入研究该WebApplication对象,你会看到它实现了IApplicationBuilder接口。

        我们将中间件管道代码剪切出来Program.cs,并将其粘贴到Configure()方法中Startup.cs。

// Startup.cs file
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
  app.UseAuthentication();
  app.UseAuthorization();

  app.MapGet("/", () =>
  {
      return "hello world";
  }).RequireAuthorization();

  app.Run();
}

        不幸的是,这实际上行不通,因为接口IApplicationBuilder没有MapGet()方法或Run()方法。那些存在于WebApplication类中。如果我们将Configure()方法更改为接受WebApplication对象,它应该可以工作。 

// Startup.cs file
public void Configure(WebApplication app, IWebHostEnvironment env)
{
  app.UseAuthentication();
  app.UseAuthorization();

  app.MapGet("/", () =>
  {
      return "hello world";
  }).RequireAuthorization();

  app.Run();
}

让我们看一下完整的文件“Program.cs”和“Startup.cs”,看看它们现在是什么样子。 

// Program.cs file
var builder = WebApplication.CreateBuilder(args);
var startup = new Startup(builder.Configuration);
startup.ConfigureServices(builder.Services);
var app = builder.Build();
startup.Configure(app, builder.Environment);

// Startup.cs file
using Microsoft.AspNetCore.Authentication.JwtBearer;

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddAuthentication(options =>
        {
            options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
        }).AddJwtBearer();
        services.AddAuthorization();
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(WebApplication app, IWebHostEnvironment env)
    {
        app.UseAuthentication();
        app.UseAuthorization();

        app.MapGet("/", () =>
        {
            return "hello world";
        }).RequireAuthorization();

        app.Run();
    }
}

        如果我们运行它,我们会得到401 Unauthorized状态,这意味着它正在运行。除了将我们的配置内容移到 中之外Startup.cs,我们没有做太多改变,这让它看起来更像旧的 .Net Core 风格。 

我们可以做得更好吗?

    使用Startup.cs确实有助于改善组织,但我认为我们可以做得更好。我一直讨厌文件中Startup.cs单词ConfigureServices()和Configure()彼此太接近,而且你还要传递一个IConfiguration对象。这可能会让人搞不清楚什么放在哪里。

    我们为什么不尝试改进这一点呢?我不喜欢这个名字ConfigureServices()。如果我们将它重命名为RegisterDependentServices()并将其放在自己的文件中会怎么样?这将使我们更容易理解发生了什么。

// RegisterDependentServices.cs file
using Microsoft.AspNetCore.Authentication.JwtBearer;

public static class RegisterDependentServices
{
    public static WebApplicationBuilder RegisterServices(this WebApplicationBuilder builder)
    {
        // Register your dependencies
        builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer();
        builder.Services.AddAuthorization();
        return builder;
    }
}

        我决定创建该类static并使用扩展方法将其添加到该类中。我们稍后WebApplicationBuilder会看到这如何改进文件。Program.cs

        接下来,让我们创建一个新文件SetupMiddlewarePipeline.cs。此文件将包含中间件管道。

public static class SetupMiddlewarePipeline
{
    public static WebApplication SetupMiddleware(this WebApplication app)
    {
        // Configure the pipeline !! ORDER MATTERS !!
        app.UseAuthorization();
        app.UseAuthentication();
        app.MapGet("/", () =>
        {
            return "hello world";
        }).RequireAuthorization();
        return app;
    }
}

现在,这会如何改变我们的Program.cs文件? 

// Program.cs file
WebApplication app = WebApplication.CreateBuilder(args)
    .RegisterServices()
    .Build();

app.SetupMiddleware()
    .Run();

这样就生成了一个非常干净的Program.cs文件。事实上,如果您愿意,您可以将其全部放在一行中。

// Program.cs file
WebApplication.CreateBuilder(args)
    .RegisterServices()
    .Build()
    .SetupMiddleware()
    .Run();

我并不讨厌它。事实上,我觉得我喜欢它。

那么 IConfiguration 怎么样?

    之前,Startup()构造函数需要IConfiguration注入其中。但是,由于WebApplication和WebApplicationBuilder都具有.Configuration属性,因此我们不再需要显式注入它。

// RegisterDependentServices.cs file
public static class RegisterDependentServices
{
  public static WebApplicationBuilder RegisterServices(this WebApplicationBuilder builder)
  {
    // ******* Access the configuration *******
    var config = builder.Configuration;

    // Register your dependencies
    builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer();
    builder.Services.AddAuthorization();
    return builder;
  }
}

// setupMiddlewarePipeline.cs file
public static class SetupMiddlewarePipeline
{
  public static WebApplication SetupMiddleware(this WebApplication app)
  {
    // ******** Access the configuration ********
    var config = app.Configuration;

    // Configure the pipeline !! ORDER MATTERS !!
    app.UseAuthorization();
    app.UseAuthentication();
    app.MapGet("/", () =>
    {
      return "hello world";
    }).RequireAuthorization();
    return app;
  }
}

如果我们需要访问应用程序的配置,它是可用的,而且我们的状况很好。 

如果您喜欢此文章,请收藏、点赞、评论,谢谢,祝您快乐每一天。 

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

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

相关文章

iOS 集成ffmpeg

前言 本来打算用flutter去实现一个ffmpeg的项目的&#xff0c;不过仔细分析了一下&#xff0c;我后期需要集成OpenGL ES做视频渲染处理&#xff0c;OpenGL ES的使用目前在flutter上面还不是很成熟&#xff0c;所以最后还是选择用原生来开发 ffmpeg集成到iOS工程 iOS对于ffmp…

基于微信小程序的移动学习平台的设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…

WPS计算机二级•幻灯片的基础操作

听说这是目录哦 PPT的正确制作步骤&#x1f6e3;️认识PPT界面布局&#x1f3dc;️PPT基础操作 快捷键&#x1f3de;️制作PPT时 常用的快捷技巧&#x1f3d9;️快速替换PPT的 文本字体&#x1f303;快速替换PPT 指定文本内容&#x1f305;能量站&#x1f61a; PPT的正确制作步…

Arcgis国产化替代:Bigemap Pro正式发布

在数字化时代&#xff0c;数据如同新时代的石油&#xff0c;蕴含着巨大的价值。从商业决策到科研探索&#xff0c;从城市规划到环境监测&#xff0c;海量数据的高效处理、精准分析与直观可视化&#xff0c;已成为各行业突破发展瓶颈、实现转型升级的关键所在。历经十年精心打磨…

GitLab配置免密登录和常用命令

SSH 免密登录 Windows免密登录 删除现有Key 访问目录&#xff1a;C:\Users\Administrator\ .ssh&#xff0c;删除公钥&#xff1a;id_rsa.pub &#xff0c;私钥&#xff1a;id_rsa 2.生成.ssh 秘钥 运行命令生成.ssh 秘钥目录&#xff08; ssh-keygen -t rsa -C xxxxxx126.…

警企联动齐发力、共筑反诈“防护墙”

2025年1月10日是第五个中国人民警察节,南通移动联合南通公安反诈中心,深入社区商圈,开展防范电信网络诈骗宣传活动,进一步增强广大人民群众的反诈意识和能力,全力守护好群众的“钱袋子”。 当日,活动现场一大早就呈现出一片忙碌景象,工作人员支起摊位,将各类精心制作的反诈宣传…

新版IDEA创建数据库表

这是老版本的IDEA创建数据库表&#xff0c;下面可以自己勾选Not null&#xff08;非空),Auto inc&#xff08;自增长),Unique(唯一标识)和Primary key&#xff08;主键) 这是新版的IDEA创建数据库表&#xff0c;Not null和Auto inc可以看得到&#xff0c;但Unique和Primary key…

分布式微服务系统简述

distributed microservice 分布式与微服务的定义及关系&#xff1b;分布式微服务架构里的各组件&#xff0c;如&#xff1a;配置中心、服务注册/发现、服务网关、负载均衡器、限流降级、断路器、服务调用、分布式事务等&#xff1b;spring cloud 介绍及实现案例&#xff0c;如…

基于Springboot + vue实现的民俗网

“前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff1a;人工智能学习网站” &#x1f496;学习知识需费心&#xff0c; &#x1f4d5;整理归纳更费神。 &#x1f389;源码免费人人喜…

Arduino大师练成手册 -- 读取DHT11

要在 Arduino 上控制 DHT11 温湿度传感器&#xff0c;你可以按照以下步骤进行&#xff1a; 硬件连接&#xff1a; 将 DHT11 的 VCC 引脚连接到 Arduino 的 5V 引脚。 将 DHT11 的 GND 引脚连接到 Arduino 的 GND 引脚。 将 DHT11 的 DATA 引脚连接到 Arduino 的数字引脚&am…

DataStream API

DataStream API是Flink的核心层API。一个Flink程序&#xff0c;其实就是对DataStream的各种转换。具体来说&#xff0c;代码基本上都由以下几部分构成&#xff1a; 一、执行环境&#xff08;Execution Environment&#xff09; Flink程序可以在各种上下文环境中运行&#xff1…

十、VUE中的CSS

一、vue中解决样式不冲突的两种方式 scoped方式 在App.vue中引入Helloworld子组件 在Helloworld子组件中再次引入我们编写Demo子组件 解释&#xff1a; 这种方式是在style上加了个scoped,限制了样式的使用范围。 动态类名方式 二、vue构建打包 npm run build

Flutter_学习记录_导航和其他

Flutter 的导航页面跳转&#xff0c;是通过组件Navigator 和 组件MaterialPageRoute来实现的&#xff0c;Navigator提供了很多个方法&#xff0c;但是目前&#xff0c;我只记录我学习过程中接触到的方法&#xff1a; Navigator.push(), 跳转下一个页面Navigator.pop(), 返回上一…

VsCode安装文档

一、下载 进入VS Code官网&#xff1a;Visual Studio Code - Code Editing. Redefined&#xff0c;点击 DownLoad for Windows下载windows版本 当然也可以点击旁边的箭头&#xff0c;下载Windows版本 或 Mac OS 版本 备注&#xff1a; Stable&#xff1a;稳定版Insiders&#…

docker Ubuntu实战

目录 Ubuntu系统环境说明 一、如何安装docker 二、发布.netcore应用到docker中 三、查看docker信息 四、保存linux服务器的镜像、下载镜像 其他 1.Error response from daemon: Get "https://registry-1.docker.io/v2/": net/http: request canceled while wa…

vue3 获取百度天气

获取百度应用key 需要开通百度天气api&#xff0c;进入 控制台 | 百度地图开放平台&#xff0c; 1、创建应用 2、填写名称 3、勾选上天气、百度地图逆地理编码 4、会得到一个key vue获取天气 应该用的是接口获取&#xff0c;这里会有跨域的问题&#xff0c;vue上用的是pro…

《论文翻译》KIMI K1.5:用大语言模型扩展强化学习

文章目录 KIMI K1.5技术报告摘要 1. 引言2. 方法&#xff1a;基于大语言模型的强化学习2.1 强化学习提示集整理2.2 长思维链监督微调2.3 强化学习2.3.1 问题设定2.3.2 策略优化2.3.3 长度惩罚2.3.4 采样策略2.3.5 训练方法的更多细节 2.4 长到短&#xff1a;短思维链模型的上下…

ESP8266 NodeMCU与WS2812灯带:实现多种花样变换

在现代电子创意项目中&#xff0c;LED灯带的应用已经变得极为广泛。通过结合ESP8266 NodeMCU的强大处理能力和FastLED库的高效功能&#xff0c;我们可以轻松实现多达100种灯带变换效果。本文将详细介绍如何使用Arduino IDE编程&#xff0c;实现从基础到高级的灯光效果&#xff…

一组开源、免费、Metro风格的 WPF UI 控件库

前言 今天大姚给大家分享一个开源、免费、Metro风格的 WPF UI 控件库&#xff1a;MahApps.Metro。 项目介绍 MahApps.Metro 是一个开源、免费、Metro风格的 WPF UI 控件库&#xff0c;提供了现代化、平滑和美观的控件和样式&#xff0c;帮助开发人员轻松创建具有现代感的 Win…

Batch Normalization学习笔记

文章目录 一、为何引入 Batch Normalization二、具体步骤1、训练阶段2、预测阶段 三、关键代码实现四、补充五、参考文献 一、为何引入 Batch Normalization 现在主流的卷积神经网络几乎都使用了批量归一化&#xff08;Batch Normalization&#xff0c;BN&#xff09;1&#xf…