ASP.NET Core 替换 Action 实际执行方法

RequestDelegate

上次,我们在《如何判断当前请求的API类型》中查看endpoints.MapControllers()实现时,最终定位到ActionEndpointFactory.cs,其中有这样一段代码:

private static RequestDelegate CreateRequestDelegate()
{
    // We don't want to close over the Invoker Factory in ActionEndpointFactory as
    // that creates cycles in DI. Since we're creating this delegate at startup time
    // we don't want to create all of the things we use at runtime until the action
    // actually matches.
    //
    // The request delegate is already a closure here because we close over
    // the action descriptor.
    IActionInvokerFactory? invokerFactory = null;

    return (context) =>
    {
        var endpoint = context.GetEndpoint()!;
        var dataTokens = endpoint.Metadata.GetMetadata<IDataTokensMetadata>();

        var routeData = new RouteData();
        routeData.PushState(router: null, context.Request.RouteValues, new RouteValueDictionary(dataTokens?.DataTokens));

        // Don't close over the ActionDescriptor, that's not valid for pages.
        var action = endpoint.Metadata.GetMetadata<ActionDescriptor>()!;
        var actionContext = new ActionContext(context, routeData, action);

        if (invokerFactory == null)
        {
            invokerFactory = context.RequestServices.GetRequiredService<IActionInvokerFactory>();
        }

        var invoker = invokerFactory.CreateInvoker(actionContext);
        return invoker!.InvokeAsync();
    };
}

从代码上理解,应该是执行请求时,会创建IActionInvokerFactory实例,由它创建 invoker 执行。

是不是这样呢,我们验证一下!

IActionInvokerFactory

新建CustomActionInvokerFactory.cs,继承IActionInvokerFactory,实现代码如下:

public class CustomActionInvokerFactory : IActionInvokerFactory
{
    private readonly IActionInvokerProvider[] _actionInvokerProviders;

    public ActionInvokerFactory(IEnumerable<IActionInvokerProvider> actionInvokerProviders)
    {
        _actionInvokerProviders = actionInvokerProviders.OrderBy(item => item.Order).ToArray();
    }

    public IActionInvoker? CreateInvoker(ActionContext actionContext)
    {
        var context = new ActionInvokerProviderContext(actionContext);

        foreach (var provider in _actionInvokerProviders)
        {
            provider.OnProvidersExecuting(context);
        }

        for (var i = _actionInvokerProviders.Length - 1; i >= 0; i--)
        {
            _actionInvokerProviders[i].OnProvidersExecuted(context);
        }

        return context.Result;
    }
}

代码 Copy 自 ASP.NET Core 内部实现类ActionInvokerFactory。

然后在 Startup.cs 注册实现:

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IActionInvokerFactory, CustomActionInvokerFactory>();
    ...
}

打上断点,执行API,发现确实如设想中一样,请求时执行CreateInvoker方法:

d6e0e7e28c5994c1139fb7d431312ce6.png

在其中发现了一个很有意思的属性MethodInfo,正是对应我们执行的 Action 方法。

突发奇想,如果我们替换MethodInfo属性,是不是会执行其他方法呢?

Demo

创建 WeatherForecast2Controller,实现代码如下:

[ApiController]
[Route("[controller]")]
public class WeatherForecast2Controller : ControllerBase
{
    [HttpGet]
    public string Get2()
    {
        return "My IO";
    }
}

可以看到这是和原方法完全不同的实现。

现在进行替换:

var actionDescriptor = actionContext.ActionDescriptor as ControllerActionDescriptor;
actionDescriptor.MethodInfo = typeof(WeatherForecast2Controller).GetMethod("Get2");

执行,出现错误提示:

6549ba76d6ecdc8a567c9cc79fb44601.png

查看ControllerActionInvokerCache.cs实现:

var objectMethodExecutor = ObjectMethodExecutor.Create(
        actionDescriptor.MethodInfo,
        actionDescriptor.ControllerTypeInfo,
        parameterDefaultValues);

原来,还要替换actionDescriptor.ControllerTypeInfo:

var actionDescriptor = actionContext.ActionDescriptor as ControllerActionDescriptor;
actionDescriptor.MethodInfo = typeof(WeatherForecast2Controller).GetMethod("Get2");
actionDescriptor.ControllerTypeInfo = typeof(WeatherForecast2Controller).GetTypeInfo();

再次运行,执行成功!

c1b5072e8a930c5abae70d1d4f490ea0.png

结论

替换 Action 实际执行方法,最好的使用场景是定制化开发,比如客户需求和产品实现完全不同,可以保证请求不变的情况下执行客户定制化实现。

想了解更多内容,请关注我的个人公众号”My IO“

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值