在 .Net Core 3.1 中更改请求路径

Shi*_*nga 5 url-routing .net-core .net-core-3.1 asp.net-core-3.1

在 3.0 之前,我可以通过访问 的HttpRequest属性HttpContext然后更改Path.

例如,要为需要更改密码的用户显示一个页面(无论用户打算访问哪个页面),我扩展了HttpContext

public static void ChangeDefaultPassword(this HttpContext context) 
=> context.Request.Path = "/Account/ChangePassword";
Run Code Online (Sandbox Code Playgroud)

这段代码将用户带入到 action 方法ChangePassword中,AccountController 而不执行用户打算访问的 action 方法。

然后进入dotnet core 3.1。

在3.1中,扩展方法改变了路径。但是,它从不执行 action 方法。它忽略更新的路径。

我知道这是由于路由的变化。现在可以使用扩展方法访问端点HttpContext.GetEndpoint()。还有一个扩展方法HttpContext.SetEndpoint似乎是设置新端点的正确方法。但是,没有关于如何完成此操作的示例。

问题

如何在不执行原始路径的情况下更改请求路径?

我试过的

  1. 我尝试改变路径。dotnet core 3.1 中的路由似乎忽略了 HttpRequest 路径值的值。
  2. 我尝试使用context.Response.Redirect("/Account/ChangePassword");. 这有效,但它首先执行用户请求的原始操作方法。这种行为违背了初衷。
  3. 我尝试使用扩展方法HttpContext.SetEndpoint,但没有可用的示例。

sch*_*nyw 7

我解决这个问题的方法是EndpointDataSource直接使用,这是一个单例服务,只要您注册了路由服务,就可以从 DI 获得该服务。只要您可以提供控制器名称和操作名称(可以在编译时指定),它就可以工作。这不需要IActionDescriptorCollectionProvider您自己使用或构建端点对象或请求委托(这非常复杂......):

public static void RerouteToActionMethod(this HttpContext context, EndpointDataSource endpointDataSource, string controllerName, string actionName)
{
    var endpoint = endpointDataSource.Endpoints.FirstOrDefault(e =>
    {
        var descriptor = e.Metadata.GetMetadata<ControllerActionDescriptor>();
        // you can add more constraints if you wish, e.g. based on HTTP method, etc
        return descriptor != null
               && actionName.Equals(descriptor.ActionName, StringComparison.OrdinalIgnoreCase)
               && controllerName.Equals(descriptor.ControllerName, StringComparison.OrdinalIgnoreCase);
    });

    if (endpoint == null)
    {
        throw new Exception("No valid endpoint found.");
    }

    context.SetEndpoint(endpoint);
}
Run Code Online (Sandbox Code Playgroud)


Shi*_*nga 2

我找到了一个可行的解决方案。我的解决方案通过使用扩展方法手动设置新端点来工作SetEndpoint

这是我为解决此问题而创建的扩展方法。

private static void RedirectToPath(this HttpContext context, string controllerName, string actionName )
{
    // Get the old endpoint to extract the RequestDelegate
    var currentEndpoint = context.GetEndpoint();

    // Get access to the action descriptor collection
    var actionDescriptorsProvider =
        context.RequestServices.GetRequiredService<IActionDescriptorCollectionProvider>();

    // Get the controller aqction with the action name and the controller name.
    // You should be redirecting to a GET action method anyways. Anyone can provide a better way of achieving this. 
    var controllerActionDescriptor = actionDescriptorsProvider.ActionDescriptors.Items
        .Where(s => s is ControllerActionDescriptor bb
                    && bb.ActionName == actionName
                    && bb.ControllerName == controllerName
                    && (bb.ActionConstraints == null
                        || (bb.ActionConstraints != null
                            && bb.ActionConstraints.Any(x => x is HttpMethodActionConstraint cc
                            && cc.HttpMethods.Contains(HttpMethods.Get)))))
        .Select(s => s as ControllerActionDescriptor)
        .FirstOrDefault();

    if (controllerActionDescriptor is null) throw new Exception($"You were supposed to be redirected to {actionName} but the action descriptor could not be found.");

    // Create a new route endpoint
    // The route pattern is not needed but MUST be present. 
    var routeEndpoint = new RouteEndpoint(currentEndpoint.RequestDelegate, RoutePatternFactory.Parse(""), 1, new EndpointMetadataCollection(new object[] { controllerActionDescriptor }), controllerActionDescriptor.DisplayName);
    
    // set the new endpoint. You are assured that the previous endpoint will never execute.
    context.SetEndpoint(routeEndpoint);
}
Run Code Online (Sandbox Code Playgroud)

重要的

  1. 您必须通过将操作方法​​放置在共享文件夹中来使其视图可用。或者,您可以决定提供一个自定义实现IViewLocationExpander
  2. 在访问端点之前,路由中间件必须已经执行。

用法

public static void ChangeDefaultPassword(this HttpContext context) 
=> context.RedirectToPath("Account","ChangePassword");
Run Code Online (Sandbox Code Playgroud)