ASP.NET MVC 4,如何在将视图模型对象用作动作方法参数之前访问/修改视图模型对象(以及更改视图和动作方法)?

时间:2013-01-21 21:12:47

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

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

如果你不明白这个问题,请参阅下面的代码示例,它应该说明我正在寻找的代码类型...... (虽然我不知道是否存在这样的接口以及将实现挂钩到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);
        }

2 个答案:

答案 0 :(得分: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
        };
    }
}

因此,您可以看到可能性无限的可能性。

答案 1 :(得分: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]'注释的方法中放置一个断点时,执行从未进入该方法。 通常,可能不希望像上面那样对控制器进行硬编码,而是可以使用反射,例如下面的第三部分库“rapidflect”:

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 Forms一起使用的“Server.Transfer”之类的东西(以及我认为可以使用相同的旧版经典ASP) )。

我已经在这个问题上看到了较旧的问题/答案,人们使用自己的一些“TransferResult”类实现了这种行为,但似乎在不同的MVC版本中它们会被破坏。 (例如,请参阅此处获取MVC 4 beta:How to redirect MVC action without returning 301? (using MVC 4 beta))。

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