4

ASP.NET MVC(MVC4)中是否有任何有用的钩子可以让您在调用操作方法之前访问操作方法参数(视图模型),然后(例如,取决于您在操作方法中检查的值)参数)让您阻止调用操作方法,即,将视图模型对象(操作方法参数)转发到另一个操作方法或直接转发到某个视图(即在操作方法中没有任何进一步处理)?

如果您不理解这个问题,请参阅下面的代码示例,它应该说明我正在寻找的代码类型......(虽然我不知道是否真的存在这种接口以及将实现挂钩的可能性MVC 框架)

如果这确实是可能的,我希望看到一个关于如何做到这一点的代码示例的答案(而不仅仅是一个声称“尝试使用方法'ActionFilterAttribute.OnActionExecuting'或'IModelBinder.BindModel'”的人的回应,因为我已经尝试过这些并且无法使其工作)。另外,请尊重我不希望这个帖子成为关于为什么这样做的讨论,而是想看看如何去做。(即,我对与诸如“您实际上想要实现的目标是什么?”或“做您想做的事情可能有更好的事情……”之类的回答进行讨论不感兴趣)

这个问题可以分成三个子问题/代码示例,因为下面我自己的代码示例试图说明:(但希望它们“重构”为使用真实现有类型的真实代码)(显然,下面的每个类型都包含子字符串有些”是我编造的,正在寻找对应的实物……)

(1) 在使用视图模型对象参数调用实际操作方法之前,如何在通用位置访问(并可能修改)视图模型对象(操作方法参数)的示例。

我正在寻找的代码示例可能与下面类似,但不知道要使用哪种接口以及如何注册它才能执行以下操作:

public class SomeClass: ISomeInterface { // How to register this kind of hook in Application_Start ?
  public void SomeMethodSomewhere(SomeActionMethodContext actionMethodContext, object actionMethodParameterViewModel) {
    string nameOfTheControllerAboutToBeInvoked = actionMethodContext.ControllerName;
    string nameOfTheActionMethodAboutToBeInvoked = actionMethodContext.MethodName;
    // the above strings are not used below but just used for illustrating that the "context object" contains information about the action method to become invoked by the MVC framework
    if(typeof(IMyBaseInterfaceForAllMyViewModels).IsAssignableFrom(actionMethodParameterViewModel.GetType())) {
        IMyBaseInterfaceForAllMyViewModels viewModel = (IMyBaseInterfaceForAllMyViewModels) actionMethodParameterViewModel;
        // check something in the view model:
        if(viewModel.MyFirstGeneralPropertyInAllViewModels == "foo") {
            // modify something in the view model before it will be passed to the target action method
            viewModel.MySecondGeneralPropertyInAllViewModels = "bar";
        }
    }
  }
}

(2) 如何阻止目标动作方法被执行而调用另一个动作方法的示例。该示例可能是上述示例的扩展,如下所示:

    public void SomeMethodSomewhere(SomeActionMethodContext actionMethodContext, object actionMethodParameterViewModel) {
        ... same as above ...
        if(viewModel.MyFirstGeneralPropertyInAllViewModels == "foo") {
            actionMethodContext.ControllerName = "SomeOtherController";
            actionMethodContext.MethodName = "SomeOtherActionMethod";
            // The above is just one example of how I imagine this kind of thing could be implemented with changing properties, and below is another example of doing it with a method invocation:
            SomeHelper.PreventCurrentlyTargetedActionMethodFromBecomingExecutedAndInsteadExecuteActionMethod("SomeOtherController", "SomeOtherActionMethod", actionMethodParameterViewModel);
            // Note that I do _NOT_ want to trigger a new http request with something like the method "Controller.RedirectToAction"
        }           

(3) 示例如何阻止执行正常的操作方法,而是将视图模型对象直接转发到视图而不进行任何进一步处理。

该示例将是上述第一个示例的扩展,如下所示:

    public void SomeMethodSomewhere(SomeActionMethodContext actionMethodContext, object actionMethodParameterViewModel) {
        ... same as the first example above ...
        if(viewModel.MyFirstGeneralPropertyInAllViewModels == "foo") {
            // the below used razor view must of course be implemented with a proper type for the model (e.g. interface 'IMyBaseInterfaceForAllMyViewModels' as used in first example above)
            SomeHelper.PreventCurrentlyTargetedActionMethodFromBecomingExecutedAndInsteadForwardViewModelToView("SomeViewName.cshtml", actionMethodParameterViewModel);
        }
4

2 回答 2

8

您可以使用操作过滤器并覆盖OnActionExecuting事件:

public class MyActionFilterAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        ...            
    }
}

现在让我们看看您可以从filterContext传递给此方法的参数中提取哪些有用信息。您应该寻找的属性被称为ActionParameters并代表一个IDictionary<string, object>. 顾名思义,此属性包含按名称和值传递给控制器​​操作的所有参数。

因此,假设您有以下控制器操作:

[MyActionFilter]
public ActionResult Index(MyViewModel model)
{
    ...
}

以下是在模型绑定后如何检索视图模型的值:

public class MyActionFilterAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var model = filterContext.ActionParameters["model"] as MyViewModel;
        // do something with the model
        // You could change some of its properties here
    }
}

现在让我们看看你问题的第二部分。如何使控制器动作短路并重定向到另一个动作?

这可以通过为属性赋值来完成Result

public class MyActionFilterAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext) 
    {
        ... some processing here and you decide to redirect:

        var routeValues = new RouteValueDictionary(new
        {
            controller = "somecontroller",
            action = "someaction"
        });
        filterContext.Result = new RedirectToRouteResult(routeValues);
    }
}

或者例如,您决定缩短控制器操作的执行并直接呈现视图:

public class MyActionFilterAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var viewResult = new ViewResult
        {
            ViewName = "~/Views/FooBar/Baz.cshtml",
        };
        MyViewModel someModel = ... get the model you want to pass to the view
        viewResult.ViewData.Model = model;
        filterContext.Result = viewResult;
    }
}

或者您可能决定呈现 JSON 结果:

public class MyActionFilterAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        MyViewModel someModel = ... get the model you want to pass to the view
        filterContext.Result = new JsonResult
        {
            Data = model,
            JsonRequestBehavior = JsonRequestBehavior.AllowGet
        };
    }
}

因此,正如您所见,您可以做的事情是无限的。

于 2013-01-21T21:38:01.840 回答
0

我对用户 Darin Dimitrov 提供的答案中的代码进行了实验,答案的第一部分和第三部分是正确的。

(虽然,对于可能找到这个线程并感兴趣的其他人,我可以澄清一下,在第一个答案中,“模型”似乎不是一个始终用于模型的硬编码关键字,但似乎必须对应于所选的名称动作方法参数。换句话说,如果你有方法签名

public ActionResult Index(MyViewModel myViewModel)

然后在你的动作过滤器中你必须使用

var model = filterContext.ActionParameters["myViewModel"] as MyViewModel;

)

关于第二个答案,“RedirectToRouteResult”的使用将触发一个新的 http 请求(正如我在我的第二个代码示例中提到的那样,这是不希望的)。通过实际显式调用它,我找到了另一种“更改”操作方法的方法:

var controller = new SomeController();
ActionResult result = controller.SomeAction(model);
filterContext.Result = result;

上面的代码实际上似乎阻止了最初的目标操作方法被调用,即当我在用'[MyActionFilter]'注释的方法中放置一个断点时,执行从未进入该方法。通常,可能不需要像上面那样对控制器进行硬编码,而是可以使用反射,例如下面的第三方库“fasterflect”:

string nameOfController = ... 
string nameOfActionMethod = ... 
// both above variables might for example be derived by using some naming convention   and parsing the refering url, depending on what you want to do ...
var theController = this.GetType().Assembly.CreateInstance(nameOfController);
ActionResult result = (ActionResult)theController.CallMethod(nameOfActionMethod, model);
filterContext.Result = result;

(对于那些想要提取当前目标控制器和动作方法的名称的人,在实现逻辑以确定要调用的控制器时,可以在过滤器中使用此代码:

var routeValueDictionary = filterContext.RouteData.Values;
string nameOfTargetedController = routeValueDictionary["controller"].ToString();
string nameOfTargetedActionMethod = routeValueDictionary["action"].ToString();

)

我认为像上面那样实例化和调用控制器感觉有点尴尬,如果可能的话,更愿意以另一种方式更改目标控制器和操作方法?那么,剩下的问题是(在 MVC 4 最终版本中)是否仍然无法在服务器“内部”重定向/转发执行(没有像“RedirectToAction”那样触发新的 http 请求)?

基本上,我想我在这里只是在寻找与 ASP.NET Web 窗体一起使用的“Server.Transfer”之类的东西(以及我相信可以使用相同东西的旧经典 ASP)。

我已经看到关于这个问题的较早的问题/答案,人们使用他们自己的一些“TransferResult”类自己实现这种行为,但它似乎倾向于在不同的 MVC 版本中被破坏。(例如,请参阅此处的 MVC 4 beta:如何在不返回 301 的情况下重定向 MVC 操作?(使用 MVC 4 beta))。

关于如何在没有新的 http 请求的情况下进行“内部重定向” (如 RedirectToAction 所做的那样),真的还没有一个简单的标准解决方案(在 MVC 4 final 中实现)吗?

于 2013-01-22T00:47:47.070 回答