【问题标题】:ASP.NET MVC 4, how to access/modify the view model object (and change view and action method) before it is used as action method parameter?ASP.NET MVC 4,如何在将视图模型对象用作操作方法参数之前访问/修改视图模型对象(并更改视图和操作方法)?
【发布时间】:2013-01-22 08:53:26
【问题描述】:

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

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

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

这个问题可以分为三个子问题/代码示例,下面我自己的代码示例试图说明: (但希望将它们“重构”为 REAL 代码并使用真实的现有类型) (很明显,下面每一个类型包含子串“Some”都是我自己编出来的,我正在寻找对应的真实的东西……)

(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);
        }

【问题讨论】:

    标签: asp.net-mvc asp.net-mvc-4 model-binding


    【解决方案1】:

    您可以使用操作过滤器并覆盖 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
            };
        }
    }
    

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

    【讨论】:

    • 我接受了这个答案,因为我认为它至少有三分之二是正确的。有关更多详细信息,请参阅我自己稍后的回复,该回复不适合此评论(允许的字符数有限)。
    【解决方案2】:

    我对用户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 测试版:How to redirect MVC action without returning 301? (using MVC 4 beta))。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-12-25
      • 2018-06-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多