3

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

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

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

4

4 回答 4

3

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

从 URL 获取 ViewResult 有 4 个步骤:

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

            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.
            }
        }
于 2013-06-25T16:13:12.280 回答
1

这里的困难在于将 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"];

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

于 2012-12-20T22:13:18.597 回答
0

试试这个:

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 与应用程序中配置的路由匹配来搜索以最小化硬编码

如何确定任意 URL 是否与定义的路由匹配

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

RouteValueDictionary 的字符串 URL

于 2012-12-20T21:36:31.007 回答
0

感谢 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();

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

于 2014-07-29T08:15:27.020 回答