.NET Core Middleware 实战:手把手写一个记录用户 IP 的中间件

把握 ASP.NET Core 请求管道,从零实现属于自己的中间件。


目录

  1. 写在前面

  2. 环境说明

  3. 什么是 Middleware?

  4. 创建空模板项目

  5. 安装日志组件

  6. 编写 IP 记录中间件
    6.1 核心 Middleware 类
    6.2 扩展方法让注册更优雅

  7. 在 Startup 中注册中间件

  8. 运行与验证

  9. 进阶:加上 IP 访问频率限制

  10. 总结与思考

  11. 互动投票


写在前面

ASP.NET Core 把所有 HTTP 请求都交给了一条由多个 Middleware 组成的管道来处理。通过编写自定义中间件,我们可以轻松地实现请求拦截、日志记录、认证授权等功能。本文将以 “记录访问者 IP” 为例,演示完整的中间件编写、注册与测试流程,并在最后附带一个简单的“访问频率限制”进阶示例。

环境说明

  • 操作系统:Windows 10

  • .NET Core SDK:1.1.x(同样适用于 2.x / 3.x / 5.x / 6.x / 7.x)

  • IDE:Visual Studio / VS Code

  • 依赖包:Microsoft.Extensions.Logging.Console

如果你使用的是更高版本的 .NET Core,只需要把下面示例中的版本号替换为对应的最新稳定版本即可。

什么是 Middleware

Middleware(中间件)位于请求处理管道中,可对 HTTP 请求进行以下操作:

  1. 读写 HttpContext

  2. 决定是否继续调用下一个中间件;

  3. 在调用后续中间件之前、之后执行自定义逻辑。

依赖注入、AOP、管道式处理模型等思想使得中间件可插拔、顺序可控,开发者只需专注实现业务逻辑,把组合交给框架。

创建空模板项目

新建 ASP.NET Core Web Application → 选择 Empty 模板,项目结构最简化,更便于观察中间件运行效果。

image.png

image.png

emptyTemplate

安装日志组件

image.png

csproj 中添加控制台日志依赖:

<ItemGroup>
  <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="1.1.2" />
</ItemGroup>

使用 dotnet restore 或 IDE 自动还原即可。

控制台日志组件便于调试,生产环境可以接入 Serilog, NLog, Elasticsearch 等更强大的日志系统。

编写 IP 记录中间件

核心 Middleware 类

RequestIPMiddleware.cs

using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System.Threading.Tasks;

namespace XzyCore.Middlewares
{
    /// <summary>
    /// 记录客户端 IP 的中间件
    /// </summary>
    public class RequestIPMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly ILogger<RequestIPMiddleware> _logger;

        public RequestIPMiddleware(RequestDelegate next,
                                   ILogger<RequestIPMiddleware> logger)
        {
            _next = next;
            _logger = logger;
        }

        public async Task Invoke(HttpContext context)
        {
            // 1. 前置逻辑:记录 IP
            var ip = context.Connection.RemoteIpAddress?.ToString() ?? "Unknown";
            _logger.LogInformation($"[RequestIPMiddleware] User IP: {ip}");

            // 2. 调用管道中的下一个中间件
            await _next(context);

            // 3. 后置逻辑(可选):例如记录响应状态码
            _logger.LogDebug($"[RequestIPMiddleware] Response Status: {context.Response.StatusCode}");
        }
    }
}

扩展方法让注册更优雅

RequestIPExtensions.cs

using Microsoft.AspNetCore.Builder;

namespace XzyCore.Middlewares
{
    public static class RequestIPExtensions
    {
        /// <summary>
        /// IApplicationBuilder 扩展,简化 UseMiddleware 调用
        /// </summary>
        public static IApplicationBuilder UseRequestIP(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<RequestIPMiddleware>();
        }
    }
}

通过扩展方法,后续在 Startup.cs 里只需要 app.UseRequestIP();,使得管道代码更加语义化。

在 Startup 中注册中间件

Startup.cs

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using XzyCore.Middlewares;

public class Startup
{
    // 注册必要服务
    public void ConfigureServices(IServiceCollection services) { }

    // 配置请求管道
    public void Configure(IApplicationBuilder app, 
                          IHostingEnvironment env,
                          ILoggerFactory loggerFactory)
    {
        // Console 日志
        loggerFactory.AddConsole();

        // 自定义 IP 记录中间件
        app.UseRequestIP();

        // 开发异常页
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        // 终结点
        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello World!");
        });
    }
}

Program.cs 中维持默认模板即可。

运行与验证

  1. dotnet run 启动项目;

  2. 浏览器访问 http://localhost:5000

  3. 控制台输出类似日志:

info: XzyCore.Middlewares.RequestIPMiddleware[0]
      [RequestIPMiddleware] User IP: 127.0.0.1

若部署到服务器,通过访问日志可获取真实的公网 IP。

进阶:加上 IP 访问频率限制

在高并发场景中,我们可能需要限制同一 IP 的访问频率以防止刷接口。下面给出一个极简示例(生产环境请使用如 AspNetCoreRateLimit 等成熟库):

public class RateLimitMiddleware
{
    private readonly RequestDelegate _next;
    private static readonly MemoryCache Cache = new MemoryCache(new MemoryCacheOptions());
    private readonly ILogger<RateLimitMiddleware> _logger;
    private const int LIMIT = 30;           // 30 秒
    private const int MAX_COUNT = 10;       // 最大 10 次

    public RateLimitMiddleware(RequestDelegate next, ILogger<RateLimitMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task Invoke(HttpContext context)
    {
        var ip = context.Connection.RemoteIpAddress?.ToString() ?? "Unknown";
        var cacheKey = $"Rate:{ip}";
        var info = Cache.GetOrCreate(cacheKey, entry =>
        {
            entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(LIMIT);
            return new RequestCounter();
        });

        if (++info.Count > MAX_COUNT)
        {
            _logger.LogWarning($"IP {ip} exceeded rate limit.");
            context.Response.StatusCode = StatusCodes.Status429TooManyRequests;
            await context.Response.WriteAsync("Too Many Requests");
            return;
        }

        await _next(context);
    }

    private class RequestCounter
    {
        public int Count { get; set; }
    }
}

注册方式同上:

app.UseMiddleware<RateLimitMiddleware>();

这样就实现了最简单的限流逻辑。

image.png

总结与思考

本文完整展示了:

  • .NET Core 请求管道与 Middleware 基本概念

  • 从零到一编写、注册、调试自定义中间件

  • 日志打印、扩展方法封装的实用技巧

  • 一个可落地的 IP 限流进阶示例

掌握中间件的设计思想后,你可以轻松扩展更多功能,例如:

  • 统一异常处理

  • JWT 认证

  • 性能埋点 / APM

  • 静态资源压缩

中间件 = 轻量 + 插拔 + 高扩展性,是 ASP.NET Core 架构的精髓之一。

互动投票

如果本文对你有所帮助,

  1. 请动动小手 点个赞 👍

  2. 觉得文章不错,收藏 ⭐ + 关注 ➕,让更多同学看到!

  3. 欢迎在评论区留言讨论,你最想实现的自定义中间件是什么?

你的每一次反馈,都是我持续创作的最大动力!谢谢~ 🎉

更多AIGC文章

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

许泽宇的技术分享

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值