关于使用Simple Injector为ASP.NET MVC控制器添加拦截器的咨询
Great question! Let's break this down into two parts: why that interface-only restriction exists, and how to add interception to your ASP.NET MVC controllers with Simple Injector.
Why the Interface-Only Restriction?
The limitation you’re hitting comes down to how Simple Injector’s default interception works under the hood—it relies on .NET’s System.Runtime.Remoting.Proxies.RealProxy type. As the comment in the code hints, RealProxy only supports creating proxies for interfaces, not concrete classes.
Concrete types can technically be proxied with RealProxy, but only if they inherit from MarshalByRefObject—a requirement that’s completely impractical for MVC controllers. It introduces unnecessary remoting overhead, doesn’t align with standard MVC controller design, and would force you to refactor all your controllers just to enable interception. Simple Injector enforces the interface check to avoid pushing you into this messy, inefficient solution.
Practical Solutions for MVC Controller Interception
Since controllers are concrete types inheriting from Controller, here are three reliable ways to add interception logic:
Solution 1: Use the Decorator Pattern (Recommended)
This is the cleanest, most framework-agnostic approach. You’ll wrap your controllers in a decorator that adds interception logic around the controller’s core execution.
- Create a controller decorator base class:
public class ControllerInterceptorDecorator : Controller { private readonly Controller _targetController; private readonly IActionInterceptor _interceptor; public ControllerInterceptorDecorator(Controller targetController, IActionInterceptor interceptor) { _targetController = targetController; _interceptor = interceptor; // Pass through the controller context to the wrapped instance ControllerContext = targetController.ControllerContext; } // Override the core execution method for MVC controllers protected override void ExecuteCore() { var actionName = ControllerContext.RouteData.Values["action"].ToString(); // Pre-execution interception logic _interceptor.BeforeAction(actionName); try { // Run the original controller's action _targetController.ExecuteCore(); } catch (Exception ex) { // Exception handling logic _interceptor.OnActionException(actionName, ex); throw; } finally { // Post-execution logic _interceptor.AfterAction(actionName); } } }
- Implement your interceptor logic:
public interface IActionInterceptor { void BeforeAction(string actionName); void AfterAction(string actionName); void OnActionException(string actionName, Exception ex); } public class LoggingActionInterceptor : IActionInterceptor { private readonly ILogger _logger; public LoggingActionInterceptor(ILogger logger) { _logger = logger; } public void BeforeAction(string actionName) => _logger.LogInformation($"Starting action: {actionName}"); public void AfterAction(string actionName) => _logger.LogInformation($"Completed action: {actionName}"); public void OnActionException(string actionName, Exception ex) => _logger.LogError(ex, $"Action failed: {actionName}"); }
- Register everything in your Simple Injector container:
// First, register all MVC controllers container.RegisterMvcControllers(Assembly.GetExecutingAssembly()); // Register your interceptor container.Register<IActionInterceptor, LoggingActionInterceptor>(Lifestyle.Singleton); // Apply the decorator to all controllers container.RegisterDecorator(typeof(Controller), typeof(ControllerInterceptorDecorator));
Solution 2: Use Castle DynamicProxy for Granular Method Interception
If you need to intercept individual action methods (instead of the entire controller execution), integrate Simple Injector with Castle DynamicProxy, which supports proxying concrete types.
- Install the required NuGet packages:
Install-Package SimpleInjector.Integration.CastleWindsor Install-Package Castle.Core
- Configure DynamicProxy and your interceptor:
// Enable DynamicProxy interception in Simple Injector container.EnableDynamicProxy(); // Implement a Castle IInterceptor public class MethodLevelInterceptor : IInterceptor { private readonly ILogger _logger; public MethodLevelInterceptor(ILogger logger) { _logger = logger; } public void Intercept(IInvocation invocation) { // Only intercept public action methods (skip helper methods) if (invocation.Method.IsPublic && !invocation.Method.IsSpecialName) { _logger.LogInformation($"Executing method: {invocation.Method.Name}"); } // Run the original method invocation.Proceed(); if (invocation.Method.IsPublic && !invocation.Method.IsSpecialName) { _logger.LogInformation($"Completed method: {invocation.Method.Name}"); } } } // Register the interceptor and apply it to all controllers container.Register<IInterceptor, MethodLevelInterceptor>(Lifestyle.Singleton); container.InterceptWith<MethodLevelInterceptor>(type => type.IsSubclassOf(typeof(Controller)));
Solution 3: Custom Controller Activator
For full control over controller creation, implement a custom IControllerActivator that wraps controllers with interception logic when they’re instantiated.
- Create the activator:
public class InterceptingActivator : IControllerActivator { private readonly Container _container; private readonly IActionInterceptor _interceptor; public InterceptingActivator(Container container, IActionInterceptor interceptor) { _container = container; _interceptor = interceptor; } public IController Create(RequestContext requestContext, Type controllerType) { // Resolve the raw controller from Simple Injector var controller = (Controller)_container.GetInstance(controllerType); // Wrap it with your interceptor decorator return new ControllerInterceptorDecorator(controller, _interceptor); } }
- Register the activator in your MVC setup:
// In Global.asax or Startup.cs ControllerBuilder.Current.SetControllerFactory( new DefaultControllerFactory(new InterceptingActivator(container, container.GetInstance<IActionInterceptor>())) );
内容的提问来源于stack exchange,提问作者boger

