通过ASP.NET MVC管道运行URL字符串以获取ActionResult

时间:2012-12-20 20:58:10

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

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

基于MVC的可扩展性,我认为这很容易。我以为我可以使用字符串URL模拟HttpRequest并将其传递给路由和控制器。我的终点是调用将返回ActionResult的action方法。我发现了我需要的点点滴滴,但是很多方法在各个类中受到保护,而且它们的文档非常稀少。

我想以某种方式进入ControllerActionInvoker并获得对受保护函数InvokeActionMethod的调用结果。

4 个答案:

答案 0 :(得分:3)

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

从网址获取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. 通过实例化新控制器子类的实例,添加事件处理程序并调用新的公共execute方法(传入模拟的RequestContext)来将所有内容绑定在一起。事件处理程序将允许您访问ViewResult。

            using (MyCompany.Controllers.MyController c = new Controllers.MyController())
            {
                c.MyActionExecuted += GrabActionResult;
                try
                {
                    c.MyExecute(requestContext);
                }
                catch (Exception)
                {
                    // Handle an exception.
                }
            }
    
  5. 这是事件处理程序:

            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.
                }
            }
    

答案 1 :(得分: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"];

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

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

How to determine if an arbitrary URL matches a defined route

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

String URL to RouteValueDictionary

答案 3 :(得分: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();

我希望这有助于其他人想要达到类似的目标。