Q:How to use RedirectToAction<T>(c=>c.SomeAction(complexObject)) without [PassParametersDuringRedirect]

577 views
Skip to first unread message

AndrewOz

unread,
Feb 26, 2010, 3:36:29 PM2/26/10
to mvccontrib-discuss
Is there a more succinct or "standard" or "MVC2" way to have complex
objects passed to target actions of RedirectToAction<T>()? In other
words, in the query string. I know I can override "ToString()" but
that seems to be using ToString for something it wasn't intended. Is
there another attribute or some other way to have the QueryString
generated automagically? using TempData as
[PassParametersDuringRedirect] does seems, well, dirty and prone to
breakage and is not future proof.

Jeremy Skinner

unread,
Feb 26, 2010, 4:02:35 PM2/26/10
to mvccontri...@googlegroups.com
At present there is no other way to do this - calling ToString is the only way to change this behaviour. This is a limitation of MVC's ExpressionHelper class (which lives in the MVC Futures assembly).

Internally, the RedirectToRouteResult<T> uses ExpressionHelper.GetRouteValuesFromExpression in order to build a RouteValueDictionary which is used when performing the redirect, so essentially the RedirectToRouteResult<T> is just a strongly typed wrapper around MVC's normal RedirectToRouteResult. So using the example you posted on the issue tracker:

var account = new Account();
return this.RedirectToAction<MyController>(c => c.Action2(account));

...is really the same as:

var account = new Account();
return RedirectToAction("Action2", new{ account = account })

The end result is exactly the same - ToString will be called on the account and "MyCode.Account" will be appended to the QueryString.

Now, what you could do is to perform your own expression tree parsing rather than using MVC's built-in ExpressionHelper. A while ago I added an additional constructor to RedirectToRouteResult<T> that takes a delegate that you can use to convert the expression tree to a RouteValueDictionary if you don't like the default behaviour. For example, rather than calling ToString you could look for a method called 'ToUrlParameter' and invoke that instead.

Here's a simple example:

public ActionResult Foo() {
  var account = new Account();
  return new MyCustomRedirectToRouteResult<MyController>(c => c.Action2(account));
}

public class MyCustomRedirectToRouteResult<T> : RedirectToRouteResult<T> where T : Controller {
    public MyCustomRedirectToRouteResult(Expression<Action<T>> expression)
        : base(expression, CustomExpressionTreeConverter) {
    }

    private static RouteValueDictionary CustomExpressionTreeConverter(Expression<Action<T>> expression) {
       //Perform custom expression tree parsing in here.
    }
}

This is the approach that I use in my own apps (I completely agree that PassParametersDuringRedirect seems 'dirty' - I don't recommend using it)

Jeremy


--
Contact Jeffrey Palermo or Eric Hexter with specific questions about the MvcContrib project.  Or go to http://mvccontrib.org

To unsubscribe from this group, send email to mvccontrib-disc...@googlegroups.com
For more options, visit this group at http://groups.google.com/group/mvccontrib-discuss?hl=en

Travis

unread,
Feb 26, 2010, 4:15:55 PM2/26/10
to mvccontri...@googlegroups.com
Will the MVC Futures assembly be ported to MVC 2.0 when it's done? I can't loose my strongly typed ActionLinks.

Jeremy Skinner

unread,
Feb 26, 2010, 4:27:00 PM2/26/10
to mvccontri...@googlegroups.com
I believe so. There is currently a build of it for rc2 at aspnet.codeplex.com so I expect there will be a build compatible with rtm shortly after release. 

Jeremy Skinner
Sent from my iPhone

Matt Hinze

unread,
Feb 26, 2010, 4:27:06 PM2/26/10
to mvccontri...@googlegroups.com
In certain situations we "serialize" the object to a dictionary that
is suitable for model binding..

http://github.com/mhinze/unbound

I was actually just thinking about getting this cleaned up and
submitted to MvcContrib..

AndrewOz

unread,
Feb 26, 2010, 4:48:14 PM2/26/10
to mvccontrib-discuss
This all seems rather strange to me. Why does the non-generic
RedirectToAction("action", "controller", model) work without any other
work necessary? Indeed, in my view RedirectToAction<T> should work
the same as the non-generic RedirectToAction or even the View("view",
model) call. Or from the client direction, the same as a post of the
form.

We've got metadata afterall... we have the technology...

Makes we want to go and do it in an acceptably generic way that would
work for most situations. The problem is, it's probably already done
somewhere in there in MVC framework, and I don't want to waste my
time. Just got to find it. Do I need to implement a custom model
binder/unbinder? Subclass System.ComponentModel.TypeConverter and use
[TypeConverter(MyTypeConverter)] on the model class?

I mean, how does RedirectToAction() or View() or RedirectToRoute() do
it now? Would that suffice? If not, can something be done to adapt
the approach they are using? Surely, somewhere in the MVC framework
is a QueryStringSerializer (or something like it already -- otherwise,
how does the post of a form get deserialized into an object?).

If someone can verify what is/is not done, then I'd be glad to
contribute... if someone can point me to the right spot to interject
the mechanism...

> On 26 February 2010 20:36, AndrewOz <andrew.os...@gmail.com> wrote:
>
>
>
> > Is there a more succinct or "standard" or "MVC2" way to have complex
> > objects passed to target actions of RedirectToAction<T>()?  In other
> > words, in the query string.  I know I can override "ToString()" but
> > that seems to be using ToString for something it wasn't intended.  Is
> > there another attribute or some other way to have the QueryString
> > generated automagically?  using TempData as
> > [PassParametersDuringRedirect] does seems, well, dirty and prone to
> > breakage and is not future proof.
>
> > --
> > Contact Jeffrey Palermo or Eric Hexter with specific questions about the

> > MvcContrib project.  Or go tohttp://mvccontrib.org

Jeremy Skinner

unread,
Feb 26, 2010, 5:58:33 PM2/26/10
to mvccontri...@googlegroups.com
> Indeed, in my view RedirectToAction<T> should work
> the same as the non-generic RedirectToAction

RedirectToAction<T> *does* work the same way as a normal RedirectToAction. The difference is to do with what you pass as the third parameter.

Example:


RedirectToAction("action", "controller", model)
vs
RedirectToAction("action", "controller", new{ model = model }).

In the first example, the model is not treated as a complex object - the MVC framework is assuming it's essentially a dictionary where all the public properties should essentially be keys. This is why in all the examples you see things like RedirectToAction("action", "controller", new { id = 1})

In the second example, the model isn't being treated as a dictionary, but rather as a *value inside the dictionary*. Using our RedirectToAction<T> is the equiavlent of doing this (or more accurately, Microsoft's ExpressionHelper class does this).

The ExpressionHelper (as you've probably gathered) is quite limited and not very extensible, which is probably why it's still in the MVC Futures assembly rather than being in the core.

Now, if you don't want to use the ExpressionHelper then you can change it. As I mentioned in the previous email, I added an additional constructor that allows you to replace the MS ExpressionHelper with your own logic for serialising the additional arguments.

> I mean, how does RedirectToAction() or View() or RedirectToRoute() do
> it now?

Let's not confuse the issue by talking about View() - this is a completely different process as there is no serialisation involved when calling View().

RedirectToAction/Route internally use a class called RouteValueDictionary. When you pass an object to it's constructor, it will essentially convert it to a dictionary with public property being a key (the Unbinder that Matt mentioned looks like it works in a similar way, but is more clever)

So for example:

var dict = new RouteValueDictionary(new{ Id = 1, Foo = "bar"});

...would produce a dictionary with the following keys/values:

Id: 1
Foo: "bar"

Likewise, if you passed a real class to this (rather than an anonymous type) you'd get the same result:

var account = new Account { UserName = "jeremy", Email = "foo@bar" };
var dict = new RouteValueDictionary(account);

...the dictionary would have the following key/values:

UserName: "jeremy"
Email: "foo@bar"

Now, if you want this behaviour to be applied to *all* the values passed to RedirectToAction<T>, then you could certainly do this by using the extensibility point I mentioned earlier and replace the ExpressionHelper with your own parser that builds the appropriate RouteValueDictionaries and then merges them together.


AndrewOz

unread,
Feb 26, 2010, 6:57:41 PM2/26/10
to mvccontrib-discuss
ahha, I see. So, the trick is to get RedirectToAction<T> to act more
like RedirectToAction("action", "controller', model). Thanks for the
hint about the extensibility point. I guess I didn't get it before.
Reply all
Reply to author
Forward
0 new messages