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站,所以等下一次某天晚上有时间会写完。

相关文章
|
1月前
|
存储 开发框架 JSON
ASP.NET Core OData 9 正式发布
【10月更文挑战第8天】Microsoft 在 2024 年 8 月 30 日宣布推出 ASP.NET Core OData 9,此版本与 .NET 8 的 OData 库保持一致,改进了数据编码以符合 OData 规范,并放弃了对旧版 .NET Framework 的支持,仅支持 .NET 8 及更高版本。新版本引入了更快的 JSON 编写器 `System.Text.UTF8JsonWriter`,优化了内存使用和序列化速度。
|
19天前
|
JSON C# 开发者
C#语言新特性深度剖析:提升你的.NET开发效率
【10月更文挑战第15天】C#语言凭借其强大的功能和易用性深受开发者喜爱。随着.NET平台的演进,C#不断引入新特性,如C# 7.0的模式匹配和C# 8.0的异步流,显著提升了开发效率和代码可维护性。本文将深入探讨这些新特性,助力开发者在.NET开发中更高效地利用它们。
28 1
|
29天前
|
存储 编译器
.Net特性Attribute的高级使用
【10月更文挑战第14天】在.NET中,特性(Attribute)是一种强大的机制,用于在代码中添加元数据。本文介绍了特性的高级用法,包括自定义特性、通过反射读取特性、条件编译与特性结合、多个特性应用以及特性继承。通过示例展示了如何创建自定义特性类、应用自定义特性,并通过反射获取特性信息。此外,还介绍了如何利用条件编译符号实现不同版本的代码控制,以及如何在一个代码元素上应用多个特性。最后,探讨了如何通过`AttributeUsage`控制特性的继承行为。
|
1月前
mcr.microsoft.com/dotnet/core/aspnet:2.1安装libgdiplus
mcr.microsoft.com/dotnet/core/aspnet:2.1安装libgdiplus
29 1
|
2月前
|
开发框架 监控 前端开发
在 ASP.NET Core Web API 中使用操作筛选器统一处理通用操作
【9月更文挑战第27天】操作筛选器是ASP.NET Core MVC和Web API中的一种过滤器,可在操作方法执行前后运行代码,适用于日志记录、性能监控和验证等场景。通过实现`IActionFilter`接口的`OnActionExecuting`和`OnActionExecuted`方法,可以统一处理日志、验证及异常。创建并注册自定义筛选器类,能提升代码的可维护性和复用性。
|
25天前
|
开发框架 JavaScript 前端开发
一个适用于 ASP.NET Core 的轻量级插件框架
一个适用于 ASP.NET Core 的轻量级插件框架
|
前端开发 .NET C#
ASP.NET Core 2 学习笔记(十)视图
原文:ASP.NET Core 2 学习笔记(十)视图 ASP.NET Core MVC中的Views是负责网页显示,将数据一并渲染至UI包含HTML、CSS等。并能痛过Razor语法在*.cshtml中写渲染画面的程序逻辑。
976 0
|
2月前
|
开发框架 前端开发 JavaScript
ASP.NET MVC 教程
ASP.NET 是一个使用 HTML、CSS、JavaScript 和服务器脚本创建网页和网站的开发框架。
38 7
|
2月前
|
存储 开发框架 前端开发
ASP.NET MVC 迅速集成 SignalR
ASP.NET MVC 迅速集成 SignalR
53 0
|
3月前
|
开发框架 前端开发 .NET
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
46 0

相关实验场景

更多