You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ASP.NET Core 6中Autofac/Castle DynamicProxy拦截器失效排查

ASP.NET Core 6中Autofac拦截器未执行的问题排查与解决

问题描述

实现ASP.NET Core 6服务时,使用Autofac配置控制器拦截器,但拦截器的Intercept方法从未触发,断点和日志均无反应。相关配置代码如下:

Program.cs中的DI配置

using Autofac;
using Autofac.Extensions.DependencyInjection;
using Autofac.Extras.DynamicProxy;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

internal class Program
{
  private static void Main(string[] args)
  {
    var builder = WebApplication.CreateBuilder(args);

    builder.Services.AddControllers();

    builder.Services.AddEndpointsApiExplorer();
    builder.Services.AddSwaggerGen();
    builder.Services.AddRequestHandlers();
    builder.Services.AddConfigurationRepository(builder.Configuration);

    builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory())
        .ConfigureContainer<ContainerBuilder>(autofacBuilder =>
    {
        autofacBuilder.RegisterType<ControllerExceptionHandlingInterceptor>();

        autofacBuilder.RegisterType<MyController>().SingleInstance()
            .EnableClassInterceptors()
            .InterceptedBy(typeof(ControllerExceptionHandlingInterceptor));
    });

    var app = builder.Build();

    if (app.Environment.IsDevelopment())
    {
        app.UseSwagger();
        app.UseSwaggerUI();
    }

    app.UseHttpsRedirection();
    app.UseAuthorization();
    app.MapControllers();

    app.Run();
  }
}

拦截器实现

using System.Threading.Tasks;
using System;
using Castle.DynamicProxy;
using IInterceptor = Castle.DynamicProxy.IInterceptor;
using IActionResult = Microsoft.AspNetCore.Mvc.IActionResult;
using Microsoft.Extensions.Logging;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Http;
using System.Reflection;

namespace Wattle.Merchant.Configuration.Host
{
  public class ControllerExceptionHandlingInterceptor : IInterceptor
  {
    private readonly ILogger<ControllerExceptionHandlingInterceptor> _logger;

    public ControllerExceptionHandlingInterceptor(
        ILogger<ControllerExceptionHandlingInterceptor> logger)
    {
        _logger = logger;
    }

    public void Intercept(IInvocation invocation)
    {
        var methodInfo = invocation.MethodInvocationTarget;
        if (IsAsync(methodInfo))
        {
            invocation.Proceed();
            invocation.ReturnValue = InterceptAsync((dynamic)invocation.ReturnValue, invocation);
        }
        else
        {
            InterceptSync(invocation);
        }
    }

    private async Task<TResult> InterceptAsync<TResult>(Task<TResult> task, IInvocation invocation)
    {
        try
        {
            return await task.ConfigureAwait(false);
        }
        catch (Exception ex)
        {
            return (TResult)HandleException(invocation, ex);
        }
    }

    private void InterceptSync(IInvocation invocation)
    {
        try
        {
            invocation.Proceed();
        }
        catch (Exception ex)
        {
            invocation.ReturnValue = HandleException(invocation, ex);
        }
    }

    private IActionResult HandleException(IInvocation invocation, Exception ex)
    {
        _logger.LogError(ex, $"{invocation.Method.Name} failed.");

        if (ex.GetType().IsAssignableFrom(typeof(ValidationException)))
            return StatusCode(StatusCodes.Status400BadRequest, ex.Message);

        if (ex.GetType().IsAssignableFrom(typeof(NotFoundException)))
            return StatusCode(StatusCodes.Status404NotFound, ex.Message);

        return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
    }

    public static bool IsAsync(MethodInfo method)
    {
        if (method.ReturnType == typeof(Task))
            return true;

        if (method.ReturnType.IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>))
            return true;

        return false;
    }

    private static ObjectResult StatusCode(
        [ActionResultStatusCode] int statusCode,
        [ActionResultObjectValue] object? value)
    {
        return new ObjectResult(value)
        {
            StatusCode = statusCode
        };
    }
  }
}

问题根源

核心问题是ASP.NET Core默认会自动实例化控制器,不会从Autofac容器中获取你注册的带拦截器的实例。builder.Services.AddControllers()已经自动将所有控制器注册到DI容器,你手动在Autofac中注册的MyController不会覆盖默认注册,导致框架使用的是未启用拦截的控制器实例。

此外,控制器使用SingleInstance()生命周期是错误的——控制器应与请求周期绑定(Scoped),单例会引发依赖注入生命周期不匹配的问题。

解决步骤

1. 配置框架从DI容器解析控制器

修改AddControllers()的配置,添加AddControllersAsServices(),让ASP.NET Core从DI容器中获取控制器实例:

builder.Services.AddControllers()
    .AddControllersAsServices(); // 关键:启用控制器的DI解析模式

2. 调整Autofac注册逻辑

方式一:批量注册所有控制器并应用拦截器(推荐)

autofacBuilder.RegisterType<ControllerExceptionHandlingInterceptor>().SingleInstance();

// 批量注册当前程序集中的所有控制器
autofacBuilder.RegisterAssemblyTypes(typeof(Program).Assembly)
    .Where(t => t.IsAssignableTo<ControllerBase>())
    .InstancePerLifetimeScope() // 匹配控制器默认的Scoped生命周期
    .EnableClassInterceptors()
    .InterceptedBy(typeof(ControllerExceptionHandlingInterceptor));

方式二:单独注册特定控制器

如果只需要拦截单个控制器,将生命周期改为InstancePerLifetimeScope并确保注册优先级:

autofacBuilder.RegisterType<ControllerExceptionHandlingInterceptor>().SingleInstance();

autofacBuilder.RegisterType<MyController>()
    .InstancePerLifetimeScope()
    .EnableClassInterceptors()
    .InterceptedBy(typeof(ControllerExceptionHandlingInterceptor))
    .AsSelf(); // 明确注册为自身类型,覆盖默认注册

3. 确保Action方法支持类拦截

使用EnableClassInterceptors()时,Autofac基于动态代理创建控制器子类,被拦截的Action方法必须是虚方法。如果不想修改Action方法,可以改用接口拦截:

  1. 定义包含Action方法的接口,让控制器实现该接口
  2. 将EnableClassInterceptors()替换为EnableInterfaceInterceptors()

额外优化点

  • 拦截器依赖ILogger(Singleton生命周期),可以将拦截器注册为SingleInstance,减少实例创建开销
  • 避免给控制器使用SingleInstance,否则会导致Scoped/Transient依赖注入异常

内容的提问来源于stack exchange,提问作者balintn

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 23:35:00