ASP.NET Core 核心特性学习笔记「上」

简介: ASP.NET Core 核心特性学习笔记「上」

学习瘾发作最严重的一次,躺在床上,拼命念大悲咒,难受的一直抓自己眼睛,眼睛越来越大都要炸开了一样,真的不知道该怎么办,我真的想学习想得要发疯了。我每时每刻眼睛都直直地盯着电脑,像一台雷达一样扫视经过我眼睛的每一个文字,我真的觉得自己像中邪了一样,我的眼睛滚烫滚烫,我发病了,我魔怔了,我要疯狂的学习,我要狠狠的学习,我受不了了,学习,我要狠狠地学习!学习,我要狠狠地学习!



开个玩笑,工作之余一直在学习SharpC2这款C2的代码,其使用了ASP.NET Core来实现,尤其是TeamServer,其实就是实现了一些遵循RESTful api规范的HTTP接口,供植入体与客户端通信使用;其中使用了很多关于ASP.NET Core的核心特性,比如依赖注入、MVC等,颇有学习价值。

本文只是晚上回家的学习过程中记的笔记,大部分都是参照图书《ASP.NET Core与RESTful API开发实战》第三章,没有太深入,旨在更好的理解SharpC2的代码,如果你也想学习SharpC2或是.NET5,那么正好。

前言

.NET Core是一个可以用来构建现代、可伸缩和高性能的跨平台软件应用程序的通用开发框架。可用于为Windows、Linux和MacOS构建软件应用程序。

ASP.NET Core本质上是一个独立的控制台应用,它并不是必需在IIS内部托管且并不需要IIS来启动运行(而这正是ASP.NET Core跨平台的基石)

启动流程:

ASP.NET Core总体启动流程

启动与宿主

1.1 启动

当ASP.NET Core应用程序启动时,他首先会配置并运行其宿主(Host),宿主主要用来启动、初始化应用程序,管理其生命周期。

可以看到Program类是程序的入口,与控制台应用程序一样。

public class Program
{
    public static void Main(string[] args)
    {
        CreateWebHostBuilder(args).Build().Run();
    }
    public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                Builder.UseStartup<Startup>();
            });
}

程序首先由CreateWebHostBuilder方法创建一个IWebHostBuilder对象,并调用Build方法得到IWebHost对象的实例,然后调用对象的Run()方法运行;还有Start()方法也可以运行,区别是前者以阻塞的方式运行。

CreateWebHostBuilder方法内部,调用WebHost类的静态方法CreateDefaultBuilder,返回IWebHostBuilder类型的对象,最后调用UseStartup方法进一步配置应用程序的启动。

用CreateDefaultBuilder方法创建IWebHostBuilder对象时所包含的主要默认选项如下:

  • 配置 Kestrel 服务器作为默认的 Web 服务器来负责处理 Web 的请求与响应
  • 使用当前目录作为应用程序的内容目录(ContentRoot),该目录决定了 ASP.NET Core 查找内容文件(如 MVC 视图等)的位置
  • 从以 ASPNETCORE_ 开头的环境变量(如 ASPNETCORE_ENVIRONMENT)中以及命令行参数中加载配置项
  • 从 appsettings.json、appsettings.{Environment}.json、用户机密(仅开发环境)、环境变量和命令行参数等位置加载应用设置
  • 配置日志功能,默认添加控制台输出和调式输出
  • 如果应用程序被托管在 IIS 中,启动 IIS 集成,它会配置应用程序的主机地址和端口,并允许捕获启动错误等

CreateDefaultBuilder的代码大致如下:

public static IWebHostBuilder CreateDefaultBuilder(string[] args)
        {
            var builder = new WebHostBuilder();
            if (string.IsNullOrEmpty(builder.GetSetting(WebHostDefaults.ContentRootKey)))
            {
                builder.UseContentRoot(Directory.GetCurrentDirectory());
            }
            if (args != null)
            {
                builder.UseConfiguration(new ConfigurationBuilder().AddCommandLine(args).Build());
            }
            builder.ConfigureAppConfiguration((hostingContext, config) =>
            {
                var env = hostingContext.HostingEnvironment;
                config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                      .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
                if (env.IsDevelopment())
                {
                    var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));
                    if (appAssembly != null)
                    {
                        config.AddUserSecrets(appAssembly, optional: true);
                    }
                }
                config.AddEnvironmentVariables();
                if (args != null)
                {
                    config.AddCommandLine(args);
                }
            })
            .ConfigureLogging((hostingContext, loggingBuilder) =>
            {
                loggingBuilder.Configure(options =>
                {
                    options.ActivityTrackingOptions = ActivityTrackingOptions.SpanId
                                                        | ActivityTrackingOptions.TraceId
                                                        | ActivityTrackingOptions.ParentId;
                });
                loggingBuilder.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
                loggingBuilder.AddConsole();
                loggingBuilder.AddDebug();
                loggingBuilder.AddEventSourceLogger();
            }).
            UseDefaultServiceProvider((context, options) =>
            {
                options.ValidateScopes = context.HostingEnvironment.IsDevelopment();
            });
            ConfigureWebDefaults(builder);
            return builder;
        }

里面的默认配置也能通过IWebHostBuilder接口提供的方法进行修改。

完整版WebHost代码:https://github.com/dotnet/aspnetcore/blob/18a926850f7374248e687ee64390e7f10514403f/src/DefaultBuilder/src/WebHost.cs

1.2 Startup类

IWebHostBuilder接口有多个扩展方法,一个重要的方法就是UseStartup方法,主要功能是向应用程序提供用于配制启动的类,应该具有两个方法

  • ConfigureServices:用于向ASP.NET Core的依赖注入容器添加服务
  • Configure:用于添加中间件,配置请求管道

两个方法都会在运行时被调用,且在应用程序的整个生命周期内,只执行一次。其中ConfigureServices是可选的Configure是必选

  • ConfigureServices方法有一个参数,为IServiceCollection类型,使用它能够将应用程序级别的服务注册到ASP.NET Core 默认的依赖注入容器中
  • Configure方法默认包函的参数类型为IApplicationBuilder,通过这个可以添加中间件

比如一个Startup类:https://github.com/dotnet/aspnetcore/blob/8b30d862de6c9146f466061d51aa3f1414ee2337/src/Mvc/test/WebSites/RazorPagesWebSite/Startup.cs

中间件

ASP.NET Core 中还有一个中间件的概念,所谓中间件,就是处理 HTTP 请求和响应的组件,它本质上是一段用来处理请求与响应的代码。多个中间件之间的链式关系使之形成了管道(Pipeline)或者请求管道。管道意味着请求将从一端进入,并按照一定的顺序由每一个中间件处理,最后由另一端出来。

ASP.NET Core 中内置了多个中间件,主要有MVC、认证、错误、静态文件、HTTPS和CORS等,也允许自定义中间件。

2.1添加中间件

上一节提到的Startup类的Configure方法就是用来添加中间件的地方,通过调用IApplicationBuilder接口中以Use开头的扩展方法即可添加系统内置的中间件,比如:

public void Configure(IApplicationBuilder app)
        {
            app.Mvc();
            app.UseAuthentication(););
        }

在这些系统内置中间件的内部实现中,每个中间件都是通过调用IApplicationBuilder接口的Use和Run方法添加到请求管道中的。

Run方法,接受一个RequestDelegate类型的参数,它是一个委托,用来处理传入的HTTP请求,定义如下:

public delegate Task RequestDelegate(HttpContext context);

Use方法不同的是,它会在处理完请求之后还会将请求传入下一个中间件做处理。

例子:

app.Use(async (context, next) =>
{
    Console.WriteLine("中间件 A:开始");
    await next();// 下一个中间件
    Console.WriteLine("中间件 A:结束");
});
app.Run(async (context) =>
{
    Console.WriteLine("中间件 B");
    await context.Response.WriteAsync("Hello, world");
});

运行结果

中间件 A:开始
中间件 B
中间件 A:结束

除了Run和Use外,IApplicationBuilder接口还提供了其他方法,可以指定一些条件来判断是否进入下一个分支管道,比如有Map、MapWhen和UseWhen。

比如,Map会根据是否匹配指定的请求路径来决定是否在一个新的分支上继续执行后续的中间件;MapWhen会对传入的HttpContext对象进行更细致的判断,比如是否包含制定的消息头等。

下面只举一个Map的例子

app.Use(async (context, next) =>
{
    Console.WriteLine("中间件 A:开始");
    await next();// 下一个中间件
    Console.WriteLine("中间件 A:结束");
});
app.Map(new PathString("/maptest"), 
    a => a.Use(async (context, next) =>
{
    Console.WriteLine("中间件 B:开始");
    await next(); // 下一个中间件
    Console.WriteLine("中间件 B:结束");
}));
app.Run(async context =>
{
    Console.WriteLine("中间件 C");
    await context.Response.WriteAsync("Hello, world");
});

访问 https://localhost:5001/maptest

中间件 A:开始
中间件 B:开始
中间件 B:结束
中间件 A:结束

可以看到新分支管道执行完毕后不会回到原来的管道,而剩下两个是会继续回去执行。

2.2自定义中间件

创建自定义中间件需要一个特定的构造函数和一个名为Invoke的方法。对于构造函数应包含一个RequestDelegate类型的参数,该参数表示在管道中的下一个中间件;而对于Invoke方法,应包含一个HttpContext类型的参数,并返回Task类型。

比如创建自定义中间件让应用程序只接受GET和HEAD方法:

public class HttpMethodCheckMiddleware
{
    // 在管道中的下一个中间件
    private readonly RequestDelegate _next;
    // 构造函数中可以得到下一个中间件,并且还可以注入需要的服务,比如 IHostEnvironment
    public HttpMethodCheckMiddleware(RequestDelegate requestDelegate, IHostEnvironment environment)
    {
        this._next = requestDelegate;
    }
    // 对 HTTP 请求方法进行判断,如果符合条件则继续执行下一个中间件
    // 否则返回 400 Bad Request 错误,并在响应中添加自定义消息头用于说明错误原因
    public Task Invoke(HttpContext context)
    {
        var requestMethod = context.Request.Method.ToUpper();
        if (requestMethod == HttpMethods.Get || requestMethod == HttpMethods.Head)
        {
            return _next(context);
        }
        else
        {
            context.Response.StatusCode = 400;
            context.Response.Headers.Add("X-AllowHTTPWeb", new[] {"GET,HEAD"});
            context.Response.WriteAsync("只支持 GET、HEAD 方法");
            return Task.CompletedTask;
        }
    }
}

在Startup类的Configure方法中添加中间件:

app.UseMiddleware<HttpMethodCheckMiddleware>();

或者创建扩展方法

public static class CustomMiddlewareExtensions
{
    public static IApplicationBuilder UseHttpMethodCheckMiddleware(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<HttpMethodCheckMiddleware>();
    }
}

调用扩展方法添加中间件

app.UseHttpMethodCheckMiddleware();




到这里上篇就结束了,下篇也写了一部分,但是夜色较晚,打算去看会B站,所以等下一次某天晚上有时间会写完。

相关文章
|
6天前
|
开发框架 .NET 开发者
简化 ASP.NET Core 依赖注入(DI)注册-Scrutor
Scrutor 是一个简化 ASP.NET Core 应用程序中依赖注入(DI)注册过程的开源库,支持自动扫描和注册服务。通过简单的配置,开发者可以轻松地从指定程序集中筛选、注册服务,并设置其生命周期,同时支持服务装饰等高级功能。适用于大型项目,提高代码的可维护性和简洁性。仓库地址:&lt;https://github.com/khellang/Scrutor&gt;
24 5
|
24天前
|
开发框架 .NET C#
在 ASP.NET Core 中创建 gRPC 客户端和服务器
本文介绍了如何使用 gRPC 框架搭建一个简单的“Hello World”示例。首先创建了一个名为 GrpcDemo 的解决方案,其中包含一个 gRPC 服务端项目 GrpcServer 和一个客户端项目 GrpcClient。服务端通过定义 `greeter.proto` 文件中的服务和消息类型,实现了一个简单的问候服务 `GreeterService`。客户端则通过 gRPC 客户端库连接到服务端并调用其 `SayHello` 方法,展示了 gRPC 在 C# 中的基本使用方法。
35 5
在 ASP.NET Core 中创建 gRPC 客户端和服务器
|
14天前
|
开发框架 缓存 .NET
GraphQL 与 ASP.NET Core 集成:从入门到精通
本文详细介绍了如何在ASP.NET Core中集成GraphQL,包括安装必要的NuGet包、创建GraphQL Schema、配置GraphQL服务等步骤。同时,文章还探讨了常见问题及其解决方法,如处理复杂查询、错误处理、性能优化和实现认证授权等,旨在帮助开发者构建灵活且高效的API。
23 3
|
28天前
|
自然语言处理 物联网 图形学
.NET 技术凭借其独特的优势和特性,为开发者们提供了一种高效、可靠且富有创造力的开发体验
本文深入探讨了.NET技术的独特优势及其在多个领域的应用,包括企业级应用、Web应用、桌面应用、移动应用和游戏开发。通过强大的工具集、高效的代码管理、跨平台支持及稳定的性能,.NET为开发者提供了高效、可靠的开发体验,并面对技术更新和竞争压力,不断创新发展。
45 7
|
27天前
|
开发框架 .NET C#
.NET 技术凭借高效开发环境、强大框架支持及跨平台特性,在软件开发中占据重要地位
.NET 技术凭借高效开发环境、强大框架支持及跨平台特性,在软件开发中占据重要地位。从企业应用到电子商务,再到移动开发,.NET 均展现出卓越性能,助力开发者提升效率与项目质量,推动行业持续发展。
27 4
|
2月前
|
JSON C# 开发者
C#语言新特性深度剖析:提升你的.NET开发效率
【10月更文挑战第15天】C#语言凭借其强大的功能和易用性深受开发者喜爱。随着.NET平台的演进,C#不断引入新特性,如C# 7.0的模式匹配和C# 8.0的异步流,显著提升了开发效率和代码可维护性。本文将深入探讨这些新特性,助力开发者在.NET开发中更高效地利用它们。
38 1
|
2月前
|
开发框架 JavaScript 前端开发
一个适用于 ASP.NET Core 的轻量级插件框架
一个适用于 ASP.NET Core 的轻量级插件框架
|
3月前
|
开发框架 前端开发 JavaScript
ASP.NET MVC 教程
ASP.NET 是一个使用 HTML、CSS、JavaScript 和服务器脚本创建网页和网站的开发框架。
46 7
|
3月前
|
存储 开发框架 前端开发
ASP.NET MVC 迅速集成 SignalR
ASP.NET MVC 迅速集成 SignalR
74 0
|
4月前
|
开发框架 前端开发 .NET
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
55 0