全栈开发:使用.NET Core WebAPI构建前后端分离的核心技巧(二)

目录

配置系统集成

分层项目使用

筛选器的使用

中间件的使用


配置系统集成

在.net core WebAPI前后端分离开发中,配置系统的设计和集成是至关重要的一部分,尤其是在管理不同环境下的配置数据时,配置系统需要能够灵活、可扩展,且易于维护。下面是配置系统集成的一些实现方式:

1)加载现有的IConfiguration

2)加载项目根目录下的appsettings.json

3)加载项目根目录下的appsettings.{Environment}.json

4)当程序运行在开发环境下,程序会加载"用户机密"配置

5)加载环境变量中的配置

6)加载命令行中的配置

在运行环境中.net core会从环境变量中读取名字为ASPNETCORE_ENVIRONMENT的值,其中推荐值:Development(开发环境)、Staging(测试环境)、Production(生产环境),其中读取的方法就是类似app.Environment.EnvironmentName、app.Environment.IsDevelopment()等,例如:

如下在入口文件中,设置了只有在开发环境下才有swagger,正式环境直接调用接口即可

在Windows和VS(推荐开发环境)中设置环境变量,可以采用如下的方法进行,右键项目打开属性,找到调试打开常规中的环境变量进行设置即可:

机密文件存放:在网上有很多新闻报道了由于公司程序员的失误操作将公司的机密数据如账户密码上传到github上去导致信息泄露,为了防止该事件的发生我们可以将不方便放到appsettings.json中的机密信息放到一个不在项目中的json文件中。

在asp.net core项目上单击鼠标右键,选择管理用户机密,在secrets.json文件中输入我们的机密信息,如下所示:

然后我们鼠标右键该文件打开文件所在目录,可以看到该文本并不在我们的项目中而是在C盘的某个文件目录下面,其中文件目录下UserSecrets后面的一串字符,就是csproj文件中的<UserSecretId>的数据,项目运行的时候也是第一时间识别该数据:

该secrets.json文件也不需要我们进行特殊读取,.net core会默认直接把它添加到我们的配置系统当中去,我们直接在入口文件通过Configuration就可以直接读取了,如下所示:

cs 复制代码
string str = app.Configuration.GetSection("conStr").Value;
Console.WriteLine(str);

注意:该方法供开发人员使用的,不适合在生产环境中使用。并且该方法仍然是明文存储,如果不想别人看到则需要采用Azure KeyVault等方法,并且无法完全避免,最重要的还是要加强安全防控意识。如果因为重装、新员工等原因导致secrets.json重建,就要重新配置,十分的麻烦,如果影响大的话还是用集中式配置服务器。

分层项目使用

在.net core webapi项目中进行项目分层是为了提高代码的可维护性、可扩展性和可测试性,通过分层设计可以将不同的关注点隔离开来,简化代码结构,使得每个层级的职责清晰,从而提高开发效率和代码质量。常见的分层方式包括:表示层、业务逻辑层、数据访问层。

例如:创建一个.net类库项目EFCoreBooks,放实体类和配置类等,在该项目下按照如下包,这里推荐都按照8版本,比较稳定,高版本可能出现版本冲突报错:

然后就是在该EFCoreBooks类库当中创建实体类和配置类,然后再创建DbContext,如下:

cs 复制代码
namespace EFCoreBooks
{
    public class MyDbContext:DbContext
    {
        public DbSet<Book> Books { get; set; }
        public MyDbContext(DbContextOptions<MyDbContext> options) : base(options)
        {

        }
        protected override void OnConfiguring(DbContextOptionsBuilder options)
        {
            base.OnConfiguring(options);
        }
        protected override void OnModelCreating(ModelBuilder model)
        {
            base.OnModelCreating(model);
            model.ApplyConfigurationsFromAssembly(this.GetType().Assembly); // 配置当前程序集下的所有配置类
        }
    }
}

设置这里我们需要设置一下有入口文件的webapi_study项目为启动项目,然后切换到我们设置实体类的项目中,执行如下命令进行数据库的迁移:

最终可以看到我们设置的实体类数据已经成功迁移到数据库当中,如下所示:

然后我们在入口文件中配置DbContext连接数据库,这里的连接文件数据使用secrets.json文件:

然后我们在控制器当中注入DbContext,然后获取数据库当中Books中的数量:

然后这里我们手动在数据库当中添加一条数据之后,发起请求打印了当前表的数量是1,没问题:

筛选器的使用

在.net core webapi中筛选器(Filters)是用于处理请求和响应的机制,可以帮助在请求处理管道中插入自定义逻辑,筛选器可用于在控制器方法执行之前或之后执行特定的代码,比如验证、日志记录、异常处理、授权等,在.net core webapi中筛选器有几种类型主要包括以下几种:

1)授权筛选器(Authorization Filters)

2)资源筛选器(Resource Filters)

3)模型绑定筛选器(Model Binding Filters)

4)操作筛选器(Action Filters)

5)异常筛选器(Exception Filters)

6)结果筛选器(Result Filters)

所有筛选器一般有同步和异步两个版本,比如IActionFilter、IAsyncActionFilter接口,接下来重点讲解Exception filter和Actionfilter这两个筛选器:如下所示:

操作筛选器:Actionfilter,在控制器方法执行之前和之后执行,可以对请求进行预处理或对响应进行后处理,多个ActionFilter是链式执行的,如下图所示:

如下我们定义一个捕获处理步骤的类:

cs 复制代码
namespace webapi_study
{
    public class MyActionFilter: IAsyncActionFilter
    {
        public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
        {
            Console.WriteLine("MyActionFilter 开始执行");
            ActionExecutedContext result = await next();
            if (result.Exception != null)
            {
                Console.WriteLine("MyActionFilter 捕获异常");
            }
            else
            {
                Console.WriteLine("MyActionFilter 执行完毕");
            }
        }
    }
}

然后我们把其注册到入口文件当中去:

cs 复制代码
builder.Services.Configure<MvcOptions>(options =>
{
    options.Filters.Add<MyActionFilter>();
});

然后我们在接口中进行调用接口,第一个接口是正常的,执行了开始到结束,第二个接口是异常的,执行了开始到捕获异常,如下所示:

案例:为了避免恶意客户端频繁发送大量请求消耗服务器资源,我们要实现"一秒钟内只允许最多有一个来自同一个IP地址的请求",在Action Filter中,如果我们不调用await next(),就可以终止Action方法的执行了,如下我们创建限制IP请求的类:

cs 复制代码
namespace webapi_study
{
    public class RateLimitActionFilter: IAsyncActionFilter
    {
        private readonly IMemoryCache memCache;
        public RateLimitActionFilter(IMemoryCache memCache)
        {
            this.memCache = memCache;
        }
        public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
        {
            string ip = context.HttpContext.Connection.RemoteIpAddress.ToString();
            string cacheKey = $"ip_{ip}";
            long? lastVisit = memCache.Get<long?>(cacheKey);
            if (lastVisit ==null || Environment.TickCount64 - lastVisit > 1000)
            {
                memCache.Set(cacheKey, Environment.TickCount64, TimeSpan.FromSeconds(10)); // 避免长期不访问的IP占用缓存
                await next();
            }
            else
            {
                ObjectResult result = new ObjectResult("访问过于频繁,请稍后再试") {StatusCode = 429};
                context.Result = result;
            }
        }
    }
}

异常筛选器:Exception Filters,在控制器方法抛出异常时执行,通常用于处理未处理的异常

如下我们定义一个捕获异常信息时候,返回设置内容的类:

cs 复制代码
namespace webapi_study
{
    public class MyExceptionFilter: IAsyncExceptionFilter
    {
        private readonly IWebHostEnvironment hostEnv;

        public MyExceptionFilter(IWebHostEnvironment hostEnv)
        {
            this.hostEnv = hostEnv;
        }
        public Task OnExceptionAsync(ExceptionContext context)
        {
            // context.Exception 代表捕获到的异常信息对象
            // context.ExceptionHandled = true; 表示捕获到异常后不再向上抛出
            // context.Result 其值会被输出到客户端
            string msg;
            if (hostEnv.IsDevelopment())
            {
                msg = context.Exception.ToString();
            }
            else
            {
                msg = "服务器内部错误";
            }
            ObjectResult result = new ObjectResult(new { code = 500, message=msg });
            context.Result = result; // 输出到客户端的内容
            context.ExceptionHandled = true; // 捕获到异常后不再向上抛出
            context.Result = result; // 输出到客户端的内容
            return Task.CompletedTask; // 异步方法返回结果
        }
    }
}

当然也可以设置一个日志记录的类,当捕获到异常之后将异常信息写入的文件当中:

cs 复制代码
namespace webapi_study
{
    public class LogExceptionFilter: IAsyncExceptionFilter
    {
        public Task OnExceptionAsync(ExceptionContext context)
        {
            return File.AppendAllTextAsync("d:/error.log", context.Exception.ToString());
        }
    }
}

然后我们在入口文件当中将这个两个异常捕获的类注入到服务当中:

cs 复制代码
builder.Services.Configure<MvcOptions>(options =>
{
    options.Filters.Add<MyExceptionFilter>();
    options.Filters.Add<LogExceptionFilter>();
});

然后我们设置一个接口,访问一个根本不存在的文件:

cs 复制代码
[HttpGet]
public string Test()
{
    string s = System.IO.File.ReadAllText("d:/123123123.txt");
    return s;
}

打印的效果如下所示,当前的code为500,文件显示不存在:

中间件的使用

中间件(Middleware):是请求处理管道中的一个重要概念,中间件是一个组件它在处理HTTP请求时拦截请求、响应,或者在请求和响应之间执行一些逻辑操作,它可以对请求进行预处理、对响应进行后处理,或在请求处理过程中添加自定义功能,如下图所示:

中间件由前逻辑、next、后逻辑3部分组成,前逻辑为第一段要执行的逻辑代码、next为指向下一个中间件的调用、后逻辑为从下一个中间件执行返回所执行的逻辑代码,每个HTTP请求都要经历一系列中间件的处理,每个中间件对于请求进行特定的处理后,再转到下一个中间件,最终的业务逻辑代码执行完成后,响应的内容也会按照处理的相反顺序进行处理,然后形成HTTP响应报文返回给客户端。

从广义上来讲Tomcat、WebLogic、Redis、lIS都是中间件,狭义上来讲ASP.NET Core中的中间件指ASP.NET Core中的一个组件。中间件组成一个管道,整个ASP.NETCore的执行过程就是HTTP请求和响应按照中间件组装的顺序在中间件之间流转的过程。开发人员可以对组成管道的中间件按照需要进行自由组合,中间件主要有以下三个概念:

1)Map:用来定义一个管道可以处理哪些请求

2)Use:每个Use引入一个中间件

3)Run:是用来执行最终的核心应用逻辑

其实这个中间件手写的话,就类似前端node中的express框架,如下我们在入口文件简单写个中间件:

cs 复制代码
using Microsoft.AspNetCore.Builder;

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

//app.MapGet("/test", () => "Hello World!");
app.Map("/test", static async (pipeBuilder) => {
    pipeBuilder.Use(async (context, next) => {
        context.Response.ContentType = "text/html";
        await context.Response.WriteAsync("1 start<br />");
        await next.Invoke();
        await context.Response.WriteAsync("1 end<br />");
    });
    pipeBuilder.Use(async (context, next) => {
        await context.Response.WriteAsync("2 start<br />");
        await next.Invoke();
        await context.Response.WriteAsync("2 end<br />");
    });
    pipeBuilder.Run(async context =>
    {
        await context.Response.WriteAsync("Run<br />");
    });
});
app.Run();

运行项目得到的结果如下所示:

相关推荐
机器人之树小风5 小时前
KUKA机器人诊断文件查看软件KUKALOGVIEWER软件
经验分享·科技·机器人
isolusion6 小时前
中间件与分布式系统:现代计算架构的核心
中间件·架构
千航@abc9 小时前
tomcat负载均衡配置
服务器·中间件·tomcat·负载均衡
LaughingZhu12 小时前
PH热榜 | 2025-03-10
前端·人工智能·经验分享·搜索引擎·产品运营
孞㐑¥12 小时前
C++vector类
开发语言·c++·经验分享·笔记
饭九钦vlog1 天前
机器人匹诺曹机制,真话假话平衡机制
服务器·经验分享·新浪微博
白水先森1 天前
如何借助 ArcGIS Pro 高效统计基站 10km 范围内的村庄数量?
经验分享·arcgispro
白水先森1 天前
ArcGIS Pro建库中常用公式的应用与技巧
经验分享·arcgis·arcgispro
白水先森1 天前
牵引线标注:让地图信息更清晰的ArcGIS Pro技巧
开发语言·javascript·经验分享·arcgis·arcgispro
亦世凡华、1 天前
快速部署:在虚拟机上安装 CentOS 7 的详细步骤
linux·运维·经验分享·centos·安装教程