views:

381

answers:

4

I'm trying to get the name of a method on a type using a lambda expression. I'm using Windows Identity Foundation and need to define access policies with the type name with namespace as a resource and the method name as the action. Here is an example.

This is the type I would be getting the type name and method name from:

namespace My.OrderEntry {
    public class Order {
        public void AddItem(string itemNumber, int quantity) {}
    }
}

This is how I would like to define the access policy through a DSL:

ForResource<Order>().Performing(o => o.AddItem).AllowUsersHaving(new Claim());

From that statement, I would like to get "My.OrderEntry.Order" as the resource and "AddItem" as the action. Getting the type name with namespace is no problem, but I don't think I can use a lambda for a method like I'm trying to do.

public static IPermissionExp Performing<T>(
    this IActionExp<T> exp,
    Func<T, delegate???> action) {} //this is where I don't know what to define

Is this sort of thing even possible to do? Is there another way to do this sort of thing without using magic strings?

A: 

You could pass it in as a Action instead, which doesn't force any return type. It is still a little messy though, because you have to pass some arguments to the method in order for it to compile.

Chris
Action would work, but I really don't care about the arguments. Could I do something like: Func<T, Action>, Func<T, Action<object>>, Func<T, Action<object, object>>
Wili
+1  A: 

I recently did a thing at work where you defined the a method using a lambda, which the internal object then took the name of. You could use strings as well, or pass in a MethodInfo but the first one isn't really type safe (and typos are a big risk), and the latter is not very elegant.

Basically I had a method like this (this is not the exact method, it is a bit more advanced):

public void SetRequest(Request req, Expression<Func<Service, Func<long, IEnumerable<Stuff>>> methodSelector);

The key here is the "Expression" thing, this lets you "select" a method like this:

SetRequest(req, service => service.SomeMethodTakingLongReturningStuffs);

Method selector is made into a expression tree which you can then fetch different bits of data from. I don't recall exactly what the resulting tree looks like, it also depends on how your lambdas look.

Skurmedel
I would have to know the method signature for that. If I have methods of different signatures, I would have to use different expressions for every signature I come across.
Wili
Yes, you could use Expression<Action> or Expression instead.
Skurmedel
I have Permorming<T>(this IActionExp<T> exp, Func<T, Expression<Action>> action). That doesn't compile.
Wili
I was incorrect in my previous comment, Expression cannot be used. However Expression<Action> can. If you look at my example the signature of the delegate to be made into a an expression tree needs to be specified as the type argument of Expression<>. So you would need to have Performing<T>(this IActionExp<T> exp, Expression<Action> action)... SLaks answer adequately shows how you would go about calling it. I don't think it can be made more "general" and prettier than his way though if you don't want to specify the signature of the method.
Skurmedel
Another "solution" would be to specify a ton of Performing-overloads with different type arguments that would cover a wide range of possible method signatures. It's not pretty implementation wise but it is an alternative.
Skurmedel
+5  A: 

There are two ways to do this:

1: You could make overloads that take the various Func and Action delegates(eg Expression<Func<T, Func<TParam1,TParam2, TReturn>>. Note that your callers would need to specify the generic parameters explicitly, either in the method call or by creating the delegate. This would be used like this:

ForResource<Order>().Performing(o => new Action<string>(o.AddItem)).AllowUsersHaving(new Claim());

2: You could take an Expression<Action> that contains a method call, and parse out the MethodInfo being called from the expression tree. This would be used like this:

ForResource<Order>().Performing(o => { o.AddItem(null); }).AllowUsersHaving(new Claim());
SLaks
for 1) could I use "object" instead of "TParam1"? for 2) any way to do that without specifying the null argument?
Wili
+1. I'm unable to come up with a more general version than your second example really.
Skurmedel
@Wili: 1) No - delegates are not covariant. 2) No.
SLaks
Not as clean as I wanted, but I was pretty sure what I wanted wasn't possible. The second example is pretty close and I decided to go with that.
Wili
A: 

It looks like this is what you are looking for if you want the name of the action delegate method passed in to the Performing function.

public static IPermissionExp Performing<T>( 
    this IActionExp<T> exp, 
    Expression<Action<T, string, int>> action) 
{
    var expression = action.Body as MethodCallExpression;
    string actionMethodName = string.Empty;
    if (expression != null)
    {
        actionMethodName = expression.Method.Name;
    }
    // use actionMethodName ("AddItem" in the case below) here
}

This would allow you to call the method like this...

ForResource<Order>().Performing((o, a, b) => o.AddItem(a, b)).AllowUsersHaving(new Claim()); 
Russell Giddings
Of course though, this requires the arguments being known and stated by the caller. Obviously, not your #1 preference given you comments on other answers :-(
Russell Giddings