0

I would like to generate a text output list that traverses my constructor dependencies for a class or list of classes. I assume I would use reflection in some way to do this? And have protection against circular dependencies.

https://stackoverflow.com/a/29704045/254257 This seems to be what I would want, but they provided no code. That question is on a similar track, but they just assume you have start with a dictionary with your dependencies already outlined as strings. So I guess how would I get that to start with.

Say I have the following:

public class UserService(IGroupService groupService, ILoggingService loggingService)
public class GroupService(IUserService userService, IRoleService roleService, ILoggingService loggingService)
public class RoleService(ILoggingService loggingService)

I would want some code to output something like this:

UserService

----GroupService

--------UserService CIRCULAR DEPENDENCY (stops going any deeper)

--------RoleService

------------LoggingService

--------LoggingService

----LoggingService

If I wanted to check dependencies on only the UserService, with the actual concrete implementation of the interfaces.

I know I can var type = typeof(UserService) as a starting point, but I've only ever worked with properties before so not sure what to do next.

I would imagine I would somehow need to get the constructor parameters, the types of those, then get the actual implementations and repeat, somehow also making sure I don't get stuck in a loop if I have any circular dependencies. Not sure how to do any of that so help would be appreciated.

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
SventoryMang
  • 10,275
  • 15
  • 70
  • 113
  • You can Start with https://stackoverflow.com/questions/1378020/how-to-get-constructor-as-methodinfo-using-reflection – Chetan May 16 '18 at 15:34

2 Answers2

0

The problem you are having stems from the fact that you are using a DI Container. Such problem is much less likely to appear when using Pure DI, since in that case the C# compiler will verify object construction, and it would be practically impossible to get such cyclic dependency.

When you use a DI Container, make sure that you use a DI Container that allows detecting cyclic dependencies and communicates a meaningful error. As a matter of fact, any of the mature DI Containers do communicate cyclic dependency errors very clearly. If your DI Container of choice does throw a stack overflow exception, please consider switching to a mature container.

Simple Injector, for instance, will throw an exception with the following message:

The configuration is invalid. Creating the instance for type IGroupService failed. The type GroupService is directly or indirectly depending on itself. The cyclic graph contains the following types: GroupService -> UserService -> GroupService.

In other words, Simple Injector shows the cyclic graph as follows:

GroupService -> UserService -> GroupService

So you don't really need object graph to be visualized, and in fact most containers won't be able to do this, because of the cyclic dependency. In case your object graph was acyclic, Simple Injector would visualize the graph as follows when you drill into the container using the Visual Studio debugger:

enter image description here

Or you can achieve the same by using Simple Injector's API programmatically:

var graph = container.GetRegistration(typeof(UserService)).VisualizeObjectGraph();

Which results in the following text:

UserService(
    GroupService(
        RoleService(
            LoggingService()),
        LoggingService()),
    LoggingService())

Please note that your mileage might vary with other DI Containers, but again, most of the more mature libraries out their contain these types of features.

Steven
  • 166,672
  • 24
  • 332
  • 435
  • 1
    Well thanks for the option, but requiring me to use a specific DI container, or saying don't use one, doesn't really answer the question in a helpful way. I would surely hope/think Microsoft.Extensions.DependencyInjection would also notify you of such an error, but I'm not sure and I just get a stackoverflow error. So I am writing this as a check to see if that's the issue. Since I can't step through the code to see where/why it's happening in that library. – SventoryMang May 16 '18 at 20:08
0

Well it took some figuring out and it's probably not perfect, but for my code it worked. I started at Chetan's comment and just went down the rabbit hole. I made it a Utility:

public static class DependencyChainUtil
{
    public static TypeModel GetDependencyChainForType(Type type)
    {
        var currentChainClassList = new List<string>();
        var model = GetDependencyChainForType(type, currentChainClassList);
        return model;
    }

    private static TypeModel GetDependencyChainForType(Type type, List<string> currentChainClassList)
    {
        if (type != null)
        {
            var model = new TypeModel() {Type = type};
            if (currentChainClassList.Any(x => x == type.FullName))
            {
                model.IsCircularReference = true;
            }
            else
            {

                currentChainClassList.Add(type.FullName);
                var constructorInfo = type.GetConstructors().Where(x => x.GetParameters().Length > 0);

                foreach (var info in constructorInfo)
                {
                    foreach (var parameterInfo in info.GetParameters())
                    {
                        var subType = parameterInfo.ParameterType;
                        if (subType.IsInterface)
                        {
                            var types = AppDomain.CurrentDomain.GetAssemblies()
                                .SelectMany(s => s.GetTypes()).Where(x => x.GetInterfaces().Contains(subType))
                                .ToList();
                            if (types.Any())
                            {
                                subType = types.FirstOrDefault();
                            }
                        }
                        model.ConstructorDependencies.Add(GetDependencyChainForType(subType, currentChainClassList));
                    }
                }

                currentChainClassList.Remove(type.FullName);
            }

            return model;
        }

        throw new ArgumentNullException("Parameter 'type' is null.");
    }

    public static string OutputTextOfDependencyChain(TypeModel model)
    {
        var output = "";
        var depth = 0;
        if (model != null)
        {
            output = OutputTextOfDependencyChain(model, output, depth);
        }

        return output;
    }

    private static string OutputTextOfDependencyChain(TypeModel model, string output, int depth)
    {
        //prepend depth markers
        output += new String(Enumerable.Range(0, depth*4).SelectMany(x => "-").ToArray());
        output += model.Type.Name;
        output += model.IsCircularReference ? "(CYCLIC DEPENDENCY)" : null;
        output += "<br/>";
        depth++;
        foreach (var typeModel in model.ConstructorDependencies)
        {
            output = OutputTextOfDependencyChain(typeModel, output, depth);
        }

        return output;
    }
}

public class TypeModel
{
    public Type Type { get; set; }
    public List<TypeModel> ConstructorDependencies { get; set; }
    public bool IsCircularReference { get; set; }

    public TypeModel()
    {
        ConstructorDependencies = new List<TypeModel>();
    }
}
SventoryMang
  • 10,275
  • 15
  • 70
  • 113