Strongly typed redirect using Linq expressions

6 views
Skip to first unread message

alwin

unread,
Feb 16, 2008, 7:59:17 PM2/16/08
to Castle Project Development List
Hey people!

After trying CodeGeneration which didn't really work out, i had a
brain fart and thought about using linq expressions to perform
strongly typed redirection. The result is below, it is really basic
and still lacks important stuff but it's about the idea :)

The idea is this:
Suppose you have controller LoginController with an action defined
with the signature Login(string username, string password)
You basically feed the RedirectToActionTyped extension method a lamda
in the form of c => c.Login("henk", "pass"). Then the extension dives
deep into the bowels of System.Linq and it invokes
LoginController.RedirectToAction("Login", {username="henk",
password="pass"})

If someone has any suggestions/comments on this technique, i'm very
interested!
To make this more useful i thought of these things:
- support [DataBind], [ARDataBind] and [ARFetch] attributes on the
parameters, perhaps with some help of SmartDispatcherController. For
example action Edit([ARDataBind("id")] Car car) now just comes up with
Edit.rails?car={car.ToString()}
- Enable redirection to any controller using the type of the target
controller and a IEngineRequest/IResponse, perhaps extend IReponse
instead. This requires the extension to find out the controller area
and name.
Oh just found out that Controller has AreaName and Name properties...
- How do i test this? With unit tests for example, is mocking needed?

Any thoughts?

Here's the code:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using Castle.MonoRail.Framework;

public static class RedirectExtensions
{
private static readonly Assembly linqAssembly =
typeof(Expression).Assembly;
private const BindingFlags methodFlags = BindingFlags.Instance |
BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

/// <summary>
/// Performs a strongly typed redirect to an action of the <paramref
name="controller"/>.
/// </summary>
/// <typeparam name="TController">The type of the controller that
has the actions.</typeparam>
/// <param name="controller">The controller that has the actions.</
param>
/// <param name="redirect">
/// A redirect expression. This must be a lamda in the form of
/// <c>c => c.Action(optional, action, parameters)</c>,
/// where the action parameters can be anything you want (field,
property, method call etc.)
/// as long as they return the right parameter and the lambda
function will compile.
/// </param>
public static void RedirectToActionTyped<TController>(
this TController controller,
Expression<Action<TController>> redirect)
where TController : Controller
{
Argument.NotNull(controller, "controller");
Argument.NotNull(redirect, "redirect");

var methodCall = redirect.Body as MethodCallExpression;
if (methodCall == null)
{
Argument.Throw("redirect",
"Redirect expression must be a MethodCallExpression, " +
"that is, a lamda in the form of 'c => c.Action(optional, action,
parameters)', " +
"where the action parameters can be anything you want (field,
property, method call etc.) " +
"as long as they return the right parameter and the function will
compile.");
}

string action = methodCall.Method.Name;

var queryParams = GetQueryParameters(methodCall);

DoTheRedirect(controller, action, queryParams);
}

private static Dictionary<string, object>
GetQueryParameters(MethodCallExpression methodCall)
{
var name2value = new Dictionary<string, object>();

if (methodCall.Arguments == null || methodCall.Arguments.Count ==
0)
{
return name2value;
}

var parameters = methodCall.Method.GetParameters();

for (int i = 0; i < parameters.Length; i++)
{
Expression argExp = methodCall.Arguments[i];
object argValue = GetParameterValue(argExp);
name2value.Add(parameters[i].Name, argValue);
}
return name2value;
}

private static object GetParameterValue(Expression param)
{
Type executorType =
linqAssembly.GetType("System.Linq.EnumerableExecutor");

MethodInfo createMethod = executorType.GetMethod("Create",
methodFlags);
object executor = createMethod.Invoke(null, new object[]
{ param });
MethodInfo executeMethod =
executor.GetType().GetMethod("ExecuteBoxed", methodFlags);

object value = executeMethod.Invoke(executor, null);
return value;
}

private static void DoTheRedirect(Controller controller, string
action, IDictionary queryStringParameters)
{
//Console.WriteLine("Invoking action {0}/{1} with {2} parameters.",
// controller.GetType().Name, action,
queryStringParameters.Count);

controller.RedirectToAction(action, queryStringParameters);
}

}

Hamilton Verissimo

unread,
Feb 16, 2008, 9:05:24 PM2/16/08
to castle-pro...@googlegroups.com
It's definitely cool. I'd like some ideas on how to integrate this
with our routing module, though.


--
Cheers,
hamilton verissimo
ham...@castlestronghold.com
http://www.castlestronghold.com/

alwin

unread,
Feb 17, 2008, 6:38:16 AM2/17/08
to Castle Project Development List
Shouldn't IResponse.RedirectUsingRoute(area, controller, action,
parameters); work? I changed the DoTheRedirect to that, and it works
for me but i don't use routing...

On Feb 17, 3:05 am, "Hamilton Verissimo"
> hamm...@castlestronghold.comhttp://www.castlestronghold.com/

Alwin

unread,
Feb 17, 2008, 6:19:38 PM2/17/08
to Castle Project Development List
Ok now you can choose to use routing or not. It's also possible to use any controller type as long as its derived from IController, and you can use any object that implements IRedirectSupport. [ARFetch] is supported, and null or empty parameter values are discarded. Also extended UrlHelper with TypedFor and TypedLink. Last but not least, worked on separation of concerns and it should be easy to customize the behaviour, or use it with Windsor for example.

Response.TypedRedirect<CarController>(c => c.List());
this.TypedRedirect<Admin.HomeController>(c => c.Index());
this.TypedRedirectToAction(c => c.Edit(theCar));

I really should get some work done instead of doing all this... ah well :)

If anyone is interested in the code i'll try to post it somewhere.

Hamilton Verissimo

unread,
Feb 17, 2008, 7:29:32 PM2/17/08
to castle-pro...@googlegroups.com
We'd appreciate a patch file.

Ken Egozi

unread,
Feb 18, 2008, 1:02:04 AM2/18/08
to castle-pro...@googlegroups.com
Hammet - a patch => drop support for .net 2.0  isn't it?
--
Ken Egozi.
http://www.kenegozi.com/blog
http://www.musicglue.com
http://www.mamaherb.com
http://www.gotfriends.co.il

Ayende Rahien

unread,
Feb 18, 2008, 1:13:00 AM2/18/08
to castle-pro...@googlegroups.com
Conditional compilation is your friend.

Ken Egozi

unread,
Feb 18, 2008, 2:16:32 AM2/18/08
to castle-pro...@googlegroups.com
it was so nice to get rid of those when dropping .net 1.1 ...

Ayende Rahien

unread,
Feb 18, 2008, 2:35:02 AM2/18/08
to castle-pro...@googlegroups.com
I am not moving to 3.0 fully for a long time yet.
And neither are a lot of our users. I don't think it would be a good move to drop 2.0 support.
We dropped 1.1 after > year of 2.0 release.

Alwin

unread,
Feb 18, 2008, 4:58:40 AM2/18/08
to castle-pro...@googlegroups.com
Yes it needs .net 3.0. But this TypedActions (couldn't think of a better name) can be in a separate assembly/project, on top of MR.
So you only need .net 3.0 if you want to use the typed actions.

Ken Egozi

unread,
Feb 18, 2008, 5:01:21 AM2/18/08
to castle-pro...@googlegroups.com
btw, Im interested to know about the walls you've hit when you tried the CodeGenerator

Alwin

unread,
Feb 18, 2008, 5:29:05 AM2/18/08
to castle-pro...@googlegroups.com
Heh you wrote that?

Well i like the code generator and it can do much more than this typed action thingy. There were some setup issues, but i was able to overcome these
- i couldn't get the msbuild task to work, but after some fiddling the code generator CmdLine worked in pre-build event
- i had to set the [ControllerDetails(Area="Admin")] on every admin controller, only on BaseAdminController didn't work
- nullable action arguments aren't really supported, the generated code looks something like System.Nullable<>System.Int32
once i got it to work and my project built, i got runtime errors, maybe something to do with [ARFetch]. I looked at the code generator code but couldn't figure it out..

I think that just keeping code generator and modify my controllers code would have been quicker than writing these typed actions :)

Ken Egozi

unread,
Feb 18, 2008, 6:00:13 AM2/18/08
to castle-pro...@googlegroups.com
Didn't write. It's the Eluetian guy's blame ... :)

I also use the cmdline, and not as a build task but I run it manually when needed (after all, I build many times a day, but add an action or a view quite rarely.

setting the Area manually on derived controller - interesting. Ill try to look at the code for that.

not using ARFetch and ARDatabind, so can't help directly with that.

Alwin

unread,
Feb 18, 2008, 6:23:58 AM2/18/08
to castle-pro...@googlegroups.com
Here's all the code, i just put it in a folder named dotnet3, in castle contrib.

On Feb 18, 2008 1:29 AM, Hamilton Verissimo <ham...@castlestronghold.com> wrote:
dotnet3 -- Castle.MonoRail.TypedActions.patch

Alwin

unread,
Feb 18, 2008, 6:28:28 AM2/18/08
to castle-pro...@googlegroups.com
Oh thanks for the build tip! It does make sense, just didn't think of it. I actually already do it that way for the QueryGenerator.
generating during Rebuild was a problem too, as the generator couldn't find the Models assembly then

Robert Ream

unread,
Feb 18, 2008, 9:43:06 AM2/18/08
to castle-pro...@googlegroups.com
For the NHQG I have been creating a separate project for the domain
queries with more intention revealing wrappers for for the more
cryptic queries. I include the domain services and repositories in
this assembly as well so that they can use the generated and hand
written queries too. I'd use a separate project for the CodeGenerator
code as well, but I can't because it creates a cyclic dependency of
sorts. So, like Ken, I only run it carefully and infrequently.

Alwin

unread,
Feb 19, 2008, 8:17:34 AM2/19/08
to castle-pro...@googlegroups.com
Yeah i do it kind of the same way now. Eventually i managed to get the CodeGenerator to work, with a little help :)

So i think the only thing that works better with the "typed actions" than with CodeGenerator is that it understands [ARFetch].

I also stumbled upon this post http://weblogs.asp.net/scottgu/archive/2007/12/03/asp-net-mvc-framework-part-2-url-routing.aspx where the MS MVC uses the same approach "Constructing Outgoing URLs from the Routing System (using Lambda Expressions)". And i thought i was being creative...

Anyway, the good news is that this probably works in AspView then, but don't know for sure as i don't use it. won't work in brail though, because boo doesn't support expression trees (yet).

James Curran

unread,
Feb 19, 2008, 10:29:18 AM2/19/08
to castle-pro...@googlegroups.com
OK, I think I'm a bit lost here...
 
How exactly is
this.RedirectToActionTyped(c => c.Login("henk", "pass"));
 
any different than:
 
this.Login("henk", "pass");
 
?
 
I could see the utility of this, if it allows jumping to a different controller:
 
RedirectToActionTyped<LoginController>(c => c.Login("henk", "pass"));
 
But, the code published (in the initial message, at least) doesn't allow that, and it still seems to be going to an awful lot of work to deconstruct the method call, just do something else can re-construct it.  Couldn't my revised method be written as:
 
              public void RedirectToActionTyped<TController>(

                       Action<TController> redirect)
                       where TController : Controller
             {
                     T controller =  _container.Resolve<T>();
                    redirect(controller);
             }


 
--
Truth,
   James

Colin Ramsay

unread,
Feb 19, 2008, 10:42:38 AM2/19/08
to castle-pro...@googlegroups.com
this.Login("henk", "pass") isn't an actual redirect though...

James Curran

unread,
Feb 19, 2008, 11:16:29 AM2/19/08
to castle-pro...@googlegroups.com
How often is passing through the http 304 mechanism important?

Alwin

unread,
Feb 19, 2008, 12:58:33 PM2/19/08
to castle-pro...@googlegroups.com
public void RedirectToActionTyped<TController>(

                       Action<TController> redirect)
                       where TController : Controller
             {
                     T controller =  _container.Resolve<T>();
                    redirect(controller);
             }
here you call just the method on the controller, instead of redirecting to it
the current action will then continue after the "redirect", and the view of the current action will be used

for me redirecting is important, as the url changes and the appropiate view is shown
with the latest code (in the patch file) you can redirect to any controller, and UrlHelper is also extended

Alwin

unread,
Jul 11, 2008, 10:11:52 PM7/11/08
to castle-pro...@googlegroups.com
Now that the contrib is open (http://groups.google.com/group/castle-project-devel/browse_thread/thread/1cdbd4092c8ac1bd?hl=en), i can put it in there if anyone is interested. But maybe there is one problem...:
There are no tests!
I can understand if this is an issue for some. Not sure if i'm ever going to write those tests. Personally i don't mind since i know it works since february and haven't touched it since then.
I'm not a TDD person yet :) i try hard though.

Minimum requirements are either .net 3.5, or .net 2.0 sp1 + system.core.dll copied to you bin directory. This should be legal according to Scott Hanselman (http://www.hanselman.com/blog/DeployingASPNETMVCOnASPNET20.aspx)
"Here's the moment of truth, and the moment we step from supported to unsupported. You can copy System.Core from your .NET 3.5 development machine (this is the machine running VS2008 that you're developing on) to the /bin folder on your .NET 2.0 SP1 machine. It's gotta be running .NET Framework 2.0 SP1 or this won't work."

If anyone wants this, then i will put it up in contrib.



On Feb 19, 7:58 pm, Alwin <alw...@gmail.com> wrote:
> public void RedirectToActionTyped<TController>(
>                        Action<TController> redirect)
>                        where TController : Controller
>              {
>                      T controller =  _container.Resolve<T>();
>                     redirect(controller);
>              }
> here you call just the method on the controller, instead of redirecting to
> it
> the current action will then continue after the "redirect", and the view of
> the current action will be used
>
> for me redirecting is important, as the url changes and the appropiate view
> is shown
> with the latest code (in the patch file) you can redirect to any controller,
> and UrlHelper is also extended
>
> On Feb 19, 2008 5:16 PM, James Curran <james.cur...@gmail.com> wrote:
>
> > How often is passing through the http 304 mechanism important?
>
> > On Feb 19, 2008 10:42 AM, Colin Ramsay <colinram...@gmail.com> wrote:
>
> > > this.Login("henk", "pass") isn't an actual redirect though...
>

Hamilton Verissimo

unread,
Jul 11, 2008, 10:28:58 PM7/11/08
to castle-pro...@googlegroups.com
sure, go for it.

--

Andre Loker

unread,
Jul 12, 2008, 3:16:59 AM7/12/08
to castle-pro...@googlegroups.com
Hi Alwin,

I'd find it cool to call Redirect<FooController>(x=>x.Foobar("bla")), so go ahead. I haven't read the discussion in February, but from what I see in the posts you quote you might want to have a look at lambda expressions (i.e. Expression<Action<TController>>) rather than to use Action<TController> directly if you want "real" redirection. I'm really looking forward to your contrib.


I can understand if this is an issue for some. Not sure if i'm ever going to write those tests. Personally i don't mind since i know it works since february and haven't touched it since then.
Just a quick note here: (unit) tests are not only there to check that something works now, but (maybe even more importantly) to automatically check that it keeps working after code changes (refactoring etc.). Having a tested code base let's me sleep much better :-) (Hint: combine the power of unit testing + code coverage + code metrics and you can greatly improve your code and design).

Have fun!
Andre

alwin

unread,
Jul 12, 2008, 11:00:12 PM7/12/08
to Castle Project Development List
Ok it's in contrib, in the typedactions directory.
If you use routing watch out, i don't use that so i don't know if that
works.
The best place to fix it is probably typedactions\trunk\Source
\Castle.MonoRail.TypedActions\Extensions\TypedActionExtensions.cs
\BuildUrlParameters

On Jul 12, 9:16 am, Andre Loker <lo...@gmx.de> wrote:
> Hi Alwin,
>
> I'd find it cool to call Redirect<FooController>(x=>x.Foobar("bla")), so
> go ahead. I haven't read the discussion in February, but from what I see
> in the posts you quote you might want to have a look at lambda
> expressions (i.e. Expression<Action<TController>>) rather than to use
> Action<TController> directly if you want "real" redirection. I'm really
> looking forward to your contrib.

Yes it is using real redirection with Expression<>, in the end its
just a strongly typed call to Response.Redirect.

> > I can understand if this is an issue for some. Not sure if i'm ever
> > going to write those tests. Personally i don't mind since i know it
> > works since february and haven't touched it since then.
>
> Just a quick note here: (unit) tests are not only there to check that
> something works now, but (maybe even more importantly) to automatically
> check that it keeps working after code changes (refactoring etc.).
> Having a tested code base let's me sleep much better :-) (Hint: combine
> the power of unit testing + code coverage + code metrics and you can
> greatly improve your code and design).

Yeah i guess you're right. The keep working aspect is important now
that you mention it.
You never know what crazy refactoring is gonna happen in the Castle
project :)

> Have fun!

Thanks, you too

Jay R. Wren

unread,
Jul 13, 2008, 9:16:53 AM7/13/08
to castle-pro...@googlegroups.com
See the comments in that thread and please don't break the .NET license. Copying system.core from a machine with 3.5 installed to a 2.0 machine is a violation of the EULA of the 3.5 framework. I'd like to strongly discourage this.  Instead, System.Core can be used from Mono (and even checked into the repo if Mono's MIT/X11 is compat with apache license) or LinqBridge can be used. In fact, Scott Hanselman's blog explicitly says "I do not know if this is legal".

--
Jay
Reply all
Reply to author
Forward
0 new messages