Get Method Name Using Lambda Expression

2020-02-09 02:46发布

问题:

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?

回答1:

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());


回答2:

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()); 


回答3:

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.



回答4:

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.