views:

53

answers:

2

I'm looking to 'generalise' some code in a .NET 3.5 MVC application and have stumbled into a problem.

Background

I have a SomeController class with some actions:

public ActionResult Renew(string qualification, int tierId) { ... }
public ActionResult Reinstate(string qualification, int tierId) { ... }
public ActionResult Withdraw(string qualification, int tierId) { ... }

In the View I use a strongly typed extension helper method to create an ActionLink (this generic method comes from Microsoft.Web.Mvc.dll). Sample use would be

Html.ActionLink<SomeController>(c=>c.Renew(Model.Qualification, Model.TierId),"Renew")

Only that I have a property on the view that defines the expression for the method call so I can do this:

Html.ActionLink(Model.Action,"Renew")

The Action property on the View Model has the following signature

public Expression<Action<SomeController>> Action { get; set; }

When I create the view in a controller action I can use the following code:

model.Action = c => c.Renew(dto.Qualification.Name, t.Id)

Approach

So far so good, all strongly typed and working nicely but this is where my question starts. I want to be able to replace the explicit call to c => c.Renew(dto.Qualification.Name, t.Id) with a parameter that is passed into the method that assembles the view model.

I was hoping to do the following:

private SomeViewModel CreateViewModel(SomeDto dto, Tier t, Func<string, int, ActionResult>> actionToCall) {
  return new SomeViewModel {
    ...
    Action = a => actionToCall(dto.Qualification.Name, t.Id), 
  }
}

and then use this method to construct the model and pass the appropriate controller action as a parameter

var model = CreateViewModel(dto,tier,this.Reinstate)

Problem

This solution compiles but when I get to rendering the ActionLink I get an expection because the expression passed in as an action is not of MethodCallExpression type. This exception is thrown by the Html.ActionLink extension method from the View.

A: 

The way this helper works is it tries to determine the URL based on the lambda expression which should always be a method call on the controller. This way it knows the action name and parameters passed. If the lambda expression is no longer a MethodCallExpression it is impossible to determine the URL.

Darin Dimitrov
Yes, I do get that. My question is how do I get the right lambda expression in (or is it possible to create on base on a passed in parameters)
mfloryan
+1  A: 

I think you want to have CreateViewModel take in an expression tree rather than a delegate. ActionLink seems to assume it gets passed an expression tree that is a lambda whose body is a method call on the parameter. If you make the same assumption about the expression passed in to CreateViewModel, you can pull out the MethodInfo object and construct the expression tree manually by doing something like this:

private SomeViewModel CreateViewModel(SomeDto dto, Tier t, 
    Expression<Func<string, int, ActionResult>> actionToCall)
{
    var methodCallExpression = (MethodCallExpression)actionToCall.Body;
    var param = Expression.Parameter(typeof(SomeController), null);
    return new SomeViewModel()
    {
        Action =
            Expression.Lambda<Action<SomeController>>(
                Expression.Call(
                    param,
                    methodCallExpression.Method,
                    Expression.Constant(dto.Qualification.Name),
                    Expression.Constant(t.Id)),
                param)
    };
}
Quartermeister
Thanks. I will give it a go. Can you not build an expression tree from a parameter though?
mfloryan
@mfloryan: You can certainly build an expression tree that calls a delegate passed in as a parameter, but the expression tree will have an InvocationExpression referencing that parameter. It sounds like ActionLink expects a MethodCallExpression instead.
Quartermeister