【问题标题】:Changing Request Path in .Net Core 3.1在 .Net Core 3.1 中更改请求路径
【发布时间】:2020-05-20 18:35:07
【问题描述】:

在 3.0 之前,我可以通过访问 HttpContextHttpRequest 属性然后更改 Path 的值来更改请求的路径(无需任何形式的浏览器重定向)。

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

public static void ChangeDefaultPassword(this HttpContext context) 
=> context.Request.Path = "/Account/ChangePassword";

这段代码将用户带到AccountController中的操作方法ChangePassword没有执行用户打算访问的操作方法。

然后进入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,但没有可用的示例。

【问题讨论】:

  • 您是否在自定义中间件中修改 URL?如果是,该中间件在管道中的哪个位置运行?
  • 我认为任何 URL 的更改都应该通过 rewrite 中间件,docs.microsoft.com/en-us/aspnet/core/fundamentals/…
  • @KirkLarkin 我没有在中间件中这样做。我有一个扩展CookieAuthenticationEvents 的类。我在验证委托人时这样做。
  • @LexLi 这不适用
  • “不适用”在哪方面?

标签: .net-core url-routing asp.net-core-3.1 .net-core-3.1


【解决方案1】:

我遇到了类似的重新路由问题。就我而言,我想在 AuthorationHandler 失败时将用户重新路由到“您没有权限”视图。我在 (.Net Core 3.1) 中应用了以下代码,特别是 (httpContext.Response.Redirect(...)) 将我路由到 Home Controller 上的 NoPermissions 操作。

在处理程序类中:

 protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, FooBarRequirement requirement) {
var hasAccess = await requirement.CheckAccess(context.User);
if (hasAccess)
context.Succeed(requirement);
else {
var message = "You do not have access to this Foobar function";
AuthorizeHandler.NoPermission(mHttpContextAccessor.HttpContext, context, requirement, message);
 }
}

我编写了一个静态类来处理重定向,传入控制器和操作期望的 url 以及错误消息,并将重定向永久标志设置为 true:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;

namespace Foo.BusinessLogic.Security {
public static class AuthorizeHandler {
    public static void NoPermission(HttpContext httpContext, 
AuthorizationHandlerContext context, IAuthorizationRequirement requirement, string 
errorMessage) {
        context.Succeed(requirement);
        httpContext.Response.Redirect($"/home/nopermission/?m={errorMessage}", true);
    }
  }
}

最后是处理视图和消息的控制器和动作

[AllowAnonymous]
public IActionResult NoPermission(string m) {
 return View("NoPermission", m);
 }
}

【讨论】:

    【解决方案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);
        }
    

    重要

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

    用法

    public static void ChangeDefaultPassword(this HttpContext context) 
    => context.RedirectToPath("Account","ChangePassword");
    

    【讨论】:

    • 使用Use扩展方法不是更好吗?
    • @HermanVanDerBlom 你可以在中间件或任何地方调用RedirectToPath
    • 这就是我在下面显示的代码中所做的。只是看起来有点不一样。让我们说更多符合 app.Use 应该做的事情:-) 如果您查看我的代码,我会在请求进一步进入管道之前拦截该请求。如果我没有实现此代码并且生成了“未找到”页面,则 IIS 服务器将显示“未找到”页面。通过使用这个中间件,我拦截了该页面并重定向到我的“未找到”页面。
    【解决方案3】:

    在我的例子中,我在 DynamicRouteValueTransformer 中手动选择匹配的端点。我有一个主要工作的解决方案,但必须切换到其他优先事项。也许其他人可以使用内置的 Action 执行器创建更优雅的解决方案。

    RequestDelegate requestDelegate = async (HttpContext x) =>
    {//manually handle controller activation, method invocation, and result processing
        var actionContext = new ActionContext(x, new RouteData(values), new ControllerActionDescriptor() { ControllerTypeInfo = controllerType.GetTypeInfo() });
        var activator = x.RequestServices.GetService(typeof(IControllerActivator)) as ServiceBasedControllerActivator;
        var controller = activator.Create(new ControllerContext(actionContext));
        var arguments = methodInfo.GetParameters().Select(p =>
        {
            object r;
            if (requestData.TryGetValue(p.Name, out object value)) r = value;
            else if (p.ParameterType.IsValueType) r = Activator.CreateInstance(p.ParameterType);
            else r = null;
            return r;
        });
        var actionResultTask = methodInfo.Invoke(controller, arguments.ToArray());
        var actionTask = actionResultTask as Task<IActionResult>;
        if (actionTask != null)
        {
            var actionResult = await actionTask;
            await actionResult.ExecuteResultAsync(actionContext);//errors here. actionContext is incomplete
        }
    };
    
    var endpoint = new Endpoint(requestDelegate, EndpointMetadataCollection.Empty, methodInfo.Name);
    httpContext.SetEndpoint(endpoint);
    

    【讨论】:

      【解决方案4】:

      我解决此问题的方法是直接使用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);
      }
      

      【讨论】:

      • 你如何使用这个?更具体地说,endpointDataSource 部分?
      • @SumNone EndpointDataSource 可以通过构造函数 DI 获得,只要您在 Startup 中调用了 services.AddRouting()。我只是将它传递给这个扩展方法
      • 就我的目的而言,这段代码就足够了。 @SumNone 使用将是 using (var scope = ctx.HttpContext.RequestServices.CreateScope()){ var eds = scope.ServiceProvider.GetRequiredService&lt;EndpointDataSource&gt;(); ctx.HttpContext.RerouteToActionMethod(eds, "MyController", "MyControllerMethod");}
      【解决方案5】:

      检查您的中间件顺序。

      .UseRouting() 公开的中间件负责根据传入的请求路径决定要访问哪个端点。如果您的路径重写中间件稍后出现在管道中(就像我的那样),那就太晚了,并且已经做出了路由决策。

      UseRouting() 之前移动我的自定义中间件可确保在路由中间件被命中之前根据需要设置路径。

      public void Configure(IApplicationBuilder app, IWebHostEnvironment env, TelemetryConfiguration telemetryConfig)
      {   
         //snip
         app.UseMiddleware<PathRewritingMiddleware>();
         app.UseRouting();
         app.UseEndpoints(endpoints =>
         {
           endpoints.MapControllers();
         });
         //snip       
      }     
      

      【讨论】:

        猜你喜欢
        • 2022-01-09
        • 2021-03-13
        • 1970-01-01
        • 1970-01-01
        • 2022-01-02
        • 2020-09-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多