【问题标题】:Run a URL string through the ASP.NET MVC pipeline to get an ActionResult通过 ASP.NET MVC 管道运行 URL 字符串以获取 ActionResult
【发布时间】:2012-12-20 20:58:09
【问题描述】:

我有一个通过查询 Google Analytics 数据获得的 URL 列表。我想通过 MVC 管道运行这些 URL 中的每一个以获取 ActionResult。动作结果包含视图模型,我可以从中提取一些重要信息。

基于 MVC 的可扩展性,我认为这很容易。我想我可以使用字符串 URL 模拟一个 HttpRequest 并通过路由和控制器传递它。我的终点是调用将返回 ActionResult 的操作方法。我正在寻找一些我需要的东西,但是很多方法都在各种类中受到保护,而且关于它们的文档非常少。

我想以某种方式访问​​ ControllerActionInvoker 并获取对受保护函数 InvokeActionMethod 的调用结果。

【问题讨论】:

  • 你如何运行这些网址?
  • 我的意思是我的输入是一个字符串URL,我想传入MVC管道并在调用action方法后提取ActionResult。
  • 是 MVC 应用程序中的这些 URL。我的意思是你是否试图在同一个应用程序中运行这些 url。
  • 网址来自 Google Analytics(分析)报告。它们是我的 MVC 网站处理的真实 URL。我想伪造对这些 URL 之一的请求,并从 ActionResult 中提取视图模型(而不是渲染视图)。
  • 我认为这篇文章会有所帮助。 haacked.com/archive/2007/12/17/…

标签: asp.net-mvc asp.net-mvc-routing actionresult


【解决方案1】:

首先,达林的回答让我开始了,但最终解决方案还有更多细节,所以我添加了一个单独的答案。这个很复杂,请耐心等待。

从 URL 获取 ViewResult 有 4 个步骤:

  1. 通过路由系统模拟 RequestContext(Darin 的回答让我开始了这个)。

            Uri uri = new Uri(MyStringUrl);
            var request = new HttpRequest(null, uri.Scheme + "://" + uri.Authority + uri.AbsolutePath, string.IsNullOrWhiteSpace(uri.Query) ? null : uri.Query.Substring(1));
            var response = new HttpResponse(new StringWriter());
            var context = new HttpContext(request, response);
            var contextBase = new HttpContextWrapper(context);
            var routeData = System.Web.Routing.RouteTable.Routes.GetRouteData(contextBase);
    
            // We shouldn't have to do this, but the way we are mocking the request doesn't seem to pass the querystring data through to the route data.
            foreach (string key in request.QueryString.Keys)
            {
                if (!routeData.Values.ContainsKey(key))
                {
                    routeData.Values.Add(key, request.QueryString[key]);
                }
            }
    
            var requestContext = new System.Web.Routing.RequestContext(contextBase, routeData);
    
  2. 子类化你的控制器。添加一个公共方法,允许您调用受保护的 Execute(RequestContext) 方法。

    public void MyExecute(System.Web.Routing.RequestContext requestContext)
    {
        this.Execute(requestContext);
    }
    
  3. 在同一个子类控制器中,添加一个挂钩到受保护的 OnActionExecuted 事件的公共事件。这允许您通过 ActionExecutedContext 获取 ViewResult。

    public delegate void MyActionExecutedHandler(ActionExecutedContext filterContext);
    
    public event MyActionExecutedHandler MyActionExecuted;
    
    protected override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        base.OnActionExecuted(filterContext);
        if (MyActionExecuted != null)
        {
            MyActionExecuted(filterContext);
        }
    }
    
  4. 通过实例化新控制器子类的实例、添加事件处理程序并调用新的公共执行方法(传入模拟的 RequestContext)将所有内容联系在一起。事件处理程序将允许您访问 ViewResult。

            using (MyCompany.Controllers.MyController c = new Controllers.MyController())
            {
                c.MyActionExecuted += GrabActionResult;
                try
                {
                    c.MyExecute(requestContext);
                }
                catch (Exception)
                {
                    // Handle an exception.
                }
            }
    

这是事件处理程序:

        private void GrabActionResult(System.Web.Mvc.ActionExecutedContext context)
        {
            if (context.Result.GetType() == typeof(ViewResult))
            {
                ViewResult result = context.Result as ViewResult;
            }
            else if (context.Result.GetType() == typeof(RedirectToRouteResult))
            {
                // Handle.
            }
            else if (context.Result.GetType() == typeof(HttpNotFoundResult))
            {
                // Handle.
            }
            else
            {
                // Handle.
            }
        }

【讨论】:

    【解决方案2】:

    这里的困难在于将 url 解析为其组成控制器和操作。这是如何做到的:

    var url = "http://example.com/Home/Index";
    var request = new HttpRequest(null, url, "");
    var response = new HttpResponse(new StringWriter.Null);
    var context = new HttpContext(request, response);
    var routeData = RouteTable.Routes.GetRouteData(new HttpContextWrapper(context));
    var values = routeData.Values;
    var controller = values["controller"];
    var action = values["action"];
    

    现在您已经了解了控制器和操作,您可以使用反射来实例化和执行它。

    【讨论】:

      【解决方案3】:

      试试这个:

      object result = null;
      Type controller = Type.GetType("MvcApplication4.Controllers.HomeController");
      if (controller != null)
      {
          object controllerObj = Activator.CreateInstance(controller, null);
          if (controller.GetMethod("ActionName") != null)
          {
              result = ((ViewResult)controller.GetMethod("ActionName").Invoke(controllerObj, null)).ViewData.Model;
          }
      }
      

      我假设在应用程序中配置了正常路由,并且可以使用正则表达式或字符串操作来检索。在您的讨论之后,我了解到你们希望通过不使用反射或任何硬编码技术来深入研究框架,从而真正遵循 MVC 管道。但是,我尝试通过遵循此线程尝试将 url 与应用程序中配置的路由匹配来搜索以最小化硬编码

      How to determine if an arbitrary URL matches a defined route

      另外,我遇​​到了创建 httprequest 以访问 routedata 对象的其他线程,但为此需要再次使用反射。

      String URL to RouteValueDictionary

      【讨论】:

      • 您的示例中的网址在哪里?您似乎所做的只是实例化一个控制器并对其调用一个操作。 OP 在他的问题中表示,他有一个 url 作为输入(例如http://example.com/Home/Index),并且他想获取提供此 url 的相应控制器操作返回的 ActionResult。
      • 自从它的MVC应用程序以来,我认为可以从URL中提取控制器和动作名称并使用foreach循环,可以替换控制器和动作名称?
      • 如果我可以模拟 HttpContext,那么我可以使用 System.Web.Routing.RouteTable.Routes.GetRouteData(MockedHttpContext) 来获取 RouteData 对象,但这就是我所掌握的。这有效地使用了我的路由中的所有逻辑来获取控制器和操作名称,但我不知道如何使用它来调用操作方法(包括解析参数和应用过滤器)。所有这些逻辑都隐藏在 ActionInvoker 中。
      • @BenMills,忘记模拟 HttpContext,你不能那样做,你最终只会浪费你的时间。
      • @DarinDimitrov 我刚刚读了一篇文章,基本上说了同样的话。那么没有办法通过路由系统传递字符串 URL 来获取 RouteData 对象吗?如果这是真的,那么我认为我的任务是不可能的。我必须使用一组新的正则表达式解析 URL 并直接调用操作方法。这将是一种耻辱,因为所有的智能都已经在我的路由中。
      【解决方案4】:

      感谢 Ben Mills,这让我开始解决自己的问题。但是,我发现通过执行以下操作,我不必执行 2、3 或 4。

      Uri uri = new Uri(MyStringUrl);
      var absoluteUri = uri.Scheme + "://" + uri.Authority + uri.AbsolutePath;
      var query = string.IsNullOrWhiteSpace(uri.Query) ? null : uri.Query.Substring(1);
      var request = new HttpRequest(null, absoluteUri, query);
      

      访问字符串编写器很重要。

      var sw = new StringWriter();
      var response = new HttpResponse(sw);
      var context = new HttpContext(request, response);
      var contextBase = new HttpContextWrapper(context);
      var routeData = System.Web.Routing.RouteTable.Routes.GetRouteData(contextBase);
      

      如果我们将 RouteData 分配给请求上下文,我们可以按预期使用 MVC 管道。

      request.RequestContext.RouteData = routeData;
      
      var controllerName = routeData.GetRequiredString("controller");
      
      var factory = ControllerBuilder.Current.GetControllerFactory();
      var contoller = factory.CreateController(request.RequestContext, controllerName);
      
      controller.Execute(request.RequestContext);
      
      var viewResult = sw.ToString(); // this is our view result.
      
      factory.ReleaseController(controller);
      sw.Dispose();
      

      我希望这可以帮助其他想要实现类似目标的人。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-05-31
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-01-09
        • 1970-01-01
        相关资源
        最近更新 更多