4
votes

I have a controller that has a few actions on it, some of them may have a custom attribute. I want to use linq to select some data into an anonymous type for each action on the controller e.g.

    Controller1
    Action1
    [MyAttribute("Con1Action2)"]
    Action2
    [MyAttribute("Con1Action3")]
    Action3


    Controller2
    Action1
    [MyAttribute("Con2Action2)"]
    Action2
    [MyAttribute("Con2Action3")]
    Action3

I want the following returned:

NameSpace = "someText", Controller = "Controller1", ActionName = "Con1Action2", 
NameSpace = "someText", Controller = "Controller1", ActionName = "Con1Action3",
NameSpace = "someText", Controller = "Controller2", ActionName = "Con2Action2", 
NameSpace = "someText", Controller = "Controller2", ActionName = "Con2Action3"

I'm struggling with a SelectMany for each action:

var controllers= myControllerList
.Where(type =>type.Namespace.StartsWith("X.") &&
    type.GetMethods().Any(m => m.GetCustomAttributes(typeof(MyAttribute)).Any()))
.SelectMany(type =>
{
    var actionNames = type.GetMethods().Where(m => m.GetCustomAttributes(typeof(MyAttribute)).Any()).ToList();

    var actionName = (MyAttribute)actionNames[0].GetCustomAttribute(typeof(MyAttribute));
    return new
    {
        Namespace = GetPath(type.Namespace),
        ActionName= actionName.Name,
        Controller = type.Name.Remove(2);
    };
}).ToList();

I'm getting an error on the SelectMany- the type arguments for the method...cannot be inferred from the usage.

3
Have you tried using Select instead of SelectMany? Each type is not a collection is it?Chris Wohlert
myControllerList is a collection and for each controller there could be multiple methods with a custome attribute, so I would need an item for each method in the controller, if that makes sense? @ChrisWohlertnewbie_86
I guess that makes sense, but in your example code, you are not trying to return all the attributes, only the first one. I'm surprised if Rui's answer isn't what you want. If it is, you should accept it as an answer. :)Chris Wohlert

3 Answers

3
votes

"SelectMany Projects each element of a sequence to an IEnumerable and flattens the resulting sequences into one sequence." Source: https://msdn.microsoft.com/en-us/library/system.linq.enumerable.selectmany(v=vs.100).aspx

You're returning one element:

return new
{
    Namespace = GetPath(type.Namespace),
    ActionName= actionName.Name,
    Controller = type.Name.Remove(2);
};

You can just use .Select

You'd use SelectMany if you wanted to get a flattened list from a property that is a colection, for example:

projects.SelectMany(p => p.Technologies).ToList()

Assuming a project has a property that is a collection named Technologies, that query would return all the technologies from all projects.

In your case because you want the list of actions per controller, you have to return a list of action information per controller:

var controllers= myControllerList
.Where(type =>type.Namespace.StartsWith("X.") &&
    type.GetMethods().Any(m => m.GetCustomAttributes(typeof(MyAttribute)).Any()))
.SelectMany(type =>
{
    var actionNames = type.GetMethods().Where(m => m.GetCustomAttributes(typeof(MyAttribute)).Any()).ToList();  

    return actionNames.Select(action => {
        var actionName = (MyAttribute)action.GetCustomAttribute(typeof(MyAttribute));
        return new
        {
             Namespace = GetPath(type.Namespace),
             ActionName= actionName.Name,
             Controller = type.Name.Remove(2);      
        });
    });    
}).ToList();
1
votes

I presume the problem is SelectMany. It expects the implementation on IEnumerable<T> on the type you are calling, in this case Type. But Type does not implement IEnumerable<T>. Hence it will error out.

Replace SelectMany by Select and you will be fine.

0
votes

As mentioned, the problem is that a single object is returned new {} where selectmany expects a collection.

In your current setup , you could do that by returning a select on the actions inside your selectmany (e.g. return actionNames.Select(a=> new {...), so that an enumerable is returned containing each separate attribute, but in this setup the attributes would be queried multiple times. (once for the check, once for the implementation)

Just a suggestion (And air coded, so might have syntax errors), but if you query all methods (selectmany), buffer the attribute per methodinfo, then check where filled, you only search for the attribute once:

var controllers= myControllerList
.Where(type =>type.Namespace.StartsWith("X."))
.SelectMany(type => type.GetMethods())
.Select(m =>  new {
   Type = m.DeclaringType, 
   Att = m.GetCustomAttribute(typeof(MyAttribute)) as MyAttribute
})
.Where(t=>t.Att!=null)
.Select(t=> new
    {
        Namespace = GetPath(t.Type.Namespace),
        ActionName= t.Att.Name,
        Controller = t.Type.Name.Remove(2);
    }
).ToList();