Getting Started with NDepend API

Introduction

The NDepend.API.dll assembly can be found in the $NDependInstallPath$\Lib directory.

Since NDepend v2021.2, NDepend.API.dll is compiled against netstandard2.0. It can then be referenced and executed from a net472 (or upper), netcore3.X (or upper) or a net50 (or upper) application.

This assembly contains a set of interfaces to develop custom sophisticated .NET static analysis tools. NDepend.API is related to CQLinq and NDepend.PowerTools:

  • CQLinq (Code Query through Linq) is a facility to develop easily lightweight static analyzer.

    CQLinq is the ability of NDepend to edit and run live linq queries (C# syntax) based on NDepend.CodeModel types, contained in NDepend.API.dll. NDepend proposes out-of-the-box around 200 code queries and rules based on CQLinq that can be used and customized easily.

  • NDepend.PowerTools are a set of short open-source static analyzers, packed into the Visual Studio solution: $NDependInstallPath$\NDepend.PowerTools.SourceCode\NDepend.PowerTools.sln

    The Power Tools demonstrate NDepend.API usage and capabilities. The solution contains one shared project used from two projects:

    • NDepend.PowerTools.csproj that compiles to the net472 assembly $NDependInstallPath$\NDepend.PowerTools.exe
    • NDepend.PowerTools.MultiOS.csproj that compiles to the net8.0 assembly $NDependInstallPath$\net8.0\NDepend.PowerTools.MultiOS.dll

    More details about the power tools can be found here.

With NDepend.API it is easy to programmatically:

  • Analyze one or several .NET assemblies and create NDepend reports (only with Build Machine licensing)
  • Explore dependencies between this assemblies namespaces, types, methods and fields
  • Gather usual code metrics, computed by NDepend on code elements, and create your own code metrics
  • Explore diff between two versions analyzed of a .NET code base, and even follow evolution across many versions
  • Open source file declaration(s) of a code element
  • Generate on the fly and execute CQLinq rules or queries

UI related API are not supported within a .NET 7.0, .NET 6.0, .NET 5.0, .NET Core 3.x context. See the list of not supported APIs here, at the bottom of the documentation.

Which license is required to run a program using NDepend.API?

100% of NDepend API can be used from:

Almost all the NDepend API can be used with a Developer license, the only exception being these two API methods:

Running an analysis and building a report can be done with Developer licensing only by clicking a button in the UI. Only the Build-Machine licensing can run automatically an analysis and automatically build a report.

NDepend API cannot be used with an Azure DevOps / TFS Extension or GitHub Action license.

Getting started with NDepend API

Certainly the best way to get started with the NDepend.API infrastructure is to use the Power Tools and browse their source code packed in the Visual Studio solution: $NDependInstallPath$\NDepend.PowerTools.SourceCode\NDepend.PowerTools.sln. You can tweak existing power tools or create your own power tools.

Also you can learn about the NDepend API types in the namespace NDepend.CodeModel by browsing the source code of the default CQLinq code queries and code rules written in top of it.

The NDepend.API.dll assembly is an access point to most of NDepend features. Internally this assembly relies on most of others NDepend assemblies. As a consequence, using this assembly cannot be as easy as copy/pasting it. At the end of this documentation, see further explanations on How to build and deploy your program using NDepend.API.

The file $NDependInstallPath$\NDepend.PowerTools.SourceCode­\NDepend_API_GettingStarted_Program.cs shows the basic code to get started with NDepend.API, here is the most relevant part. Types and methods linked are the backbone of the API:

using System;
using System.Collections.Generic;
using System.Linq;
 
using NDepend.Analysis;
using NDepend.CodeModel;
using NDepend.Path;
using NDepend.Project;
 
namespace YourNamespace {
   class Program {
 
      public static void Main() {
         // 0) Creates a NDependServicesProvider object
         var ndependServicesProvider = new NDependServicesProvider();
 
 
         // 1) obtain some VS solution or project file path
         var visualStudioManager = ndependServicesProvider.VisualStudioManager;
         ICollection<IAbsoluteFilePath> vsSlnOrProjFilePaths;
         // get a non IntPtr.Zero owner Window Handle. 
         // If you are in a System.Console try this.
         IntPtr ownerWindowHandle = ...; 
         visualStudioManager.ShowDialogSelectVisualStudioSolutionsOrProjects(
            ownerWindowHandle, out vsSlnOrProjFilePaths);
         // Could also use:  
         //  visualStudioManager.GetMostRecentlyUsedVisualStudioSolutionOrProject()
 
 
         // 2) obtains assemblies file path to analyze
         var assembliesFilePath = 
            (from vsSlnOrProjFilePath in vsSlnOrProjFilePaths
             from assembliesFilePathTmp in 
visualStudioManager.GetAssembliesFromVisualStudioSolutionOrProject(vsSlnOrProjFilePath)
             select assembliesFilePathTmp).Distinct().ToArray();
 
         // 3) gets or create a IProject object
         var projectManager = ndependServicesProvider.ProjectManager;
         IProject project = projectManager.CreateTemporaryProject(
            assembliesFilePath, 
            TemporaryProjectMode.Temporary);
         // Or, to get a IProject object, could also use
         //   var project = projectManager.CreateBlankProject()  to create a new project
         //   and then project.CodeToAnalyze.SetApplicationAssemblies(...)
         //    or project.CodeToAnalyze.SetIDEFiles(
         //        new [] { new IDEFile("C:\File.sln".ToAbsoluteFilePath()","-test") } )
         //   and then projectManager.SaveProject(project);   to save the project file
         //
         // Or, to get an existing IProject object, could also use
         //    projectManager.ShowDialogChooseAnExistingProject(out project)
         // Or programmatically list most recently used NDepend projects on this machine
         // with this call:
         //    projectManager.GetMostRecentlyUsedProjects()
 
 
         // 4) gets an IAnalysisResult object from the IProject object
         //    **> Both RunAnalysis() and RunAnalysisAndBuildReport() methods 
         //        work only with a Build Machine license
         IAnalysisResult analysisResult = project.RunAnalysis();
         //  Or  project.RunAnalysisAndBuildReport()  
         
 
         // Or, to get a IAnalysisResult object, first gets a IAnalysisResultRef object, 
         // that represents a reference to a persisted IAnalysisResult object
         // then call
         //    project.TryGetMostRecentAnalysisResultRef() or 
         //    project.GetAvailableAnalysisResultsRefs() or 
         //    project.GetAvailableAnalysisResultsRefsGroupedPerMonth()
         // and then call 
         //    analysisResultRef.Load()
 
 
         // 5) gets a ICodeBase object from the IAnalysisResult object
         ICodeBase codeBase = analysisResult.CodeBase;
         // Or eventually use a ICompareContext object if you wish to analyze
         // the code delta between two ICodeBase.
         // codeBase.CreateCompareContextWithOlder(olderCodeBase)
 
 
         // 6) Use the code model API to query code and do develop any algorithm you need!
         //    For example here we are looking for complex methods
         var complexMethods = (from m in codeBase.Application.Methods
                               where m.ILCyclomaticComplexity > 10
                               orderby m.ILCyclomaticComplexity descending
                               select m).ToArray();
         if (complexMethods.Length == 0) { return; }
         Console.WriteLine(
            $"Press a key to show the {complexMethods.Length} most complex methods");
         Console.ReadKey();
         foreach (var m in complexMethods) {
            Console.WriteLine(
               $"{m.FullName} has a IL cyclomatic complexity of {m.ILCyclomaticComplexity}");
         }
 
 
         // 7) eventually lets the user opens source file declaration
         if (complexMethods.First().SourceFileDeclAvailable) {
            var mostComplexMethod = complexMethods.First();
            Console.WriteLine(
               "Press a key to open the source code decl of the most complex method?");
            Console.ReadKey();
            mostComplexMethod.TryOpenSource();
            // Eventually use 
            //   ExtensionMethodsTooling.TryCompareSourceWith(
            //     NDepend.CodeModel.ISourceFileLine,
            //     NDepend.CodeModel.ISourceFileLine)
            // to compare 2 different versions of a code element
         }
      }
   }
}

How to build and deploy your programs that use NDepend.API?

To create your own program that references the assembly NDepend.API.dll there are a few requirements:

  • NDepend.API.dll Reference Properties (in Visual Studio) must have Copy Local set to False.

    visual studio project copy local equals false

  • You must include in your project the source file $NDependInstallPath$\NDepend.PowerTools.SourceCode\AssemblyResolver.cs to load NDepend.API.dll at runtime (see also the source code AssemblyResolver.cs below).
    In your Main() method you must use AssemblyResolver this way (see the code sample below).
    Notice that if your executable is not generated in $NDependInstallDir$ the relative path .\Lib (highlighted in the code below) must be updated to provide a relative path to this directory.
    using System;
    using System.Linq;
    using System.Runtime.CompilerServices;
     
    using NDepend;
    using NDepend.Analysis;
    using NDepend.Path;
    using NDepend.PowerTools;
    using NDepend.Project;
     
    namespace YourNamespace {
       class Program {
     
          // ************************** IMPORTANT ***********************************
          // All programs using NDepend.API.dll should have this type AssemblyResolver
          // parametrized with the relative path to the dir "$NDependInstallDir$\Lib".
          // Since  NDepend.PowerTool.exe  is in the dir "$NDependInstallDir$"
          // the relative path is @".\Lib"
          private static readonly AssemblyResolver s_AssemblyResolver = new AssemblyResolver(@".\Lib");
     
          [STAThread]
          static void Main() {
             AppDomain.CurrentDomain.AssemblyResolve += s_AssemblyResolver.AssemblyResolveHandler;
             MainSub();
          }
     
          // Need this MethodImplAttribute to make sure that AssemblyResolve
          // has been registered successfully before JITing any method
          // that uses anything from the NDepend.API
          [MethodImpl(MethodImplOptions.NoInlining)]
          static void MainSub() {
             var ndependServicesProvider = new NDependServicesProvider();
     
             // Example of using the NDepend.API
             var projectManager = ndependServicesProvider.ProjectManager;
             IProject project = projectManager.LoadProject(
                @"C:\YourPathToYourNDependProjectFile\File.ndproj".ToAbsoluteFilePath());
     
             // Try to load the most recent analysis result
             var refs = project.GetAvailableAnalysisResultsRefs();
             if (!refs.Any()) { return; }
             IAnalysisResult result = refs[0].Load();
     
             // Obtain source files paths from the analysis result code base model 
             // For example this can be done this way:
             var sourceFiles = result.CodeBase.Application.CodeElements
                .Where(c => c.SourceFileDeclAvailable)
                .SelectMany(c => c.SourceDecls)
                .Select(c => c.SourceFile.FilePath)
                .Distinct()
                .ToList();
     
             // Print all source files paths
             foreach (IAbsoluteFilePath sourceFilePath in sourceFiles) {
                Console.WriteLine(sourceFilePath.ToString());
             }
          }
       }
    }
  • NDepend assemblies other than NDepend.API.dll contain the code behind the interfaces defined in NDepend.API.dll, hence all NDepend assemblies must be present in $NDependInstallPath$\, $NDependInstallPath$\Lib and $NDependInstallPath$\net8.0.
  • NDepend licensing must have been registered on a machine before redeploying a program that references the assembly NDepend.API.dll on it.
  • As an alternative of using the class AssemblyResolver you can create an App.Config file for your executable assembly, and add a <probing> element that refers to $NDependInstallPath$\Lib (like for example C:\NDepend\Lib). When the CLR won't resolve an assembly, then it'll look at dir in such element

APIs not supported within a .NET 7.0, .NET 6.0, .NET 5.0, .NET Core 3.x context

netstandard 2.0 assemblies can be consumed from any .NET Framework 4.7.2 or 4.8 project. Since NDepend v2021.2 NDepend.API.dll is a netstandard 2.0 assembly and all its APIs can be used within a .NET Framework 4.7.2 or 4.8 context.

netstandard 2.0 assemblies can be consumed from any .NET 7.0, .NET 6.0, .NET 5.0, .NET Core 3.x project. However there are a few UI or Windows related APIs in NDepend.API.dll that are not supported within such context. As a consequence, in such context those APIs throw a NotSupportedException.

Note that in the NDepend.PowerTools.MultiOS project sources (found within $NDependInstallPath$\NDepend.PowerTools.SourceCode\NDepend.PowerTools.sln) these APIs are not invoked thanks to the usage of a NETCORE compilation symbol.

Here is the list:


Example: Export a code rule or query result to json

This code can be used in any .NET Framework or .NET Core (3,5,6...) console project.

using System;
using System.Runtime.CompilerServices;
using System.Linq;
using System.IO;
using NDepend;
using NDepend.Path;
using NDepend.Project;
using NDepend.Analysis;
using NDepend.CodeQuery;
 
public class ExportQueryResult {
 
   
   private static readonly string SEP = Path.DirectorySeparatorChar.ToString();
   private static readonly AssemblyResolver s_AssemblyResolver = new AssemblyResolver(
         // TODO Update the path to $NDependInstallDir$\Lib\NDepend.API.dll
         //      relative to the path of the present assembly
         $@"..{SEP}..{SEP}..{SEP}..{SEP}..{SEP}bin{SEP}Debug{SEP}Lib"
         );
 
   public static void Main(string[] args) {
      AppDomain.CurrentDomain.AssemblyResolve += s_AssemblyResolver.AssemblyResolveHandler;
      MainSub(args);
   }
 
   // MainSub() is here to avoids that the Main() method uses something
   // from NDepend.API without having registered AssemblyResolveHandler!
   [MethodImpl(MethodImplOptions.NoInlining)]
   public static void MainSub(string[] args) {
 
      // Init NDepend.API
      var ndependServicesProvider = new NDependServicesProvider();
 
      // Load the NDepend project
      IProject project = ndependServicesProvider.ProjectManager.LoadProject(
         @"C:\Projects\GifRecorder.ndproj".ToAbsoluteFilePath());
 
      // Load the most recent analysis result
      if(!project.TryGetMostRecentAnalysisResultRef(out IAnalysisResultRef @ref)) { return; }
      IAnalysisResult aResult = @ref.Load();
 
      // Get the query
      IQuery query = project.CodeQueries.CodeQueriesSet.AllQueriesRecursive.FirstOrDefault(
         q => q.QueryString.Contains("Avoid types with too many methods"));
      if(query == null) { return; }
 
      // Compile the query against the codeBase
      IQueryCompiled qCompiled = query.QueryString.Compile(aResult.CodeBase);
      if(qCompiled.HasErrors) { return; }
 
      // Execute the query compiled
      IQueryExecutionResult qResult = qCompiled.QueryCompiledSuccess.Execute(aResult.CodeBase);
      if(qResult.Status != QueryExecutionStatus.Success) { return; }
 
      // Export the query result (DocumentKind can be HTML, XML, CSV, Excel...)
      string jsonContent = qResult.ExportQueryResult(QueryResultExportDocumentKind.JSON, QueryResultExportFlags.UnfoldMultiItemsCell);
      File.WriteAllText(@"C:\temp\result.json", jsonContent);
   }
}

Annexe: AssemblyResolver.cs

When you want to run a project against NDepend.API, you need to have this class in your project as explained in the section How to build and deploy your programs that use NDepend.API?.

using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
 
internal class AssemblyResolver {
 
   internal AssemblyResolver(string relativePathToLib) {
      // Assert we have a relative path to the NDepend lib folder!
      Debug.Assert(!string.IsNullOrEmpty(relativePathToLib));
      Debug.Assert(relativePathToLib.Length >= 5);
      Debug.Assert(relativePathToLib[0] == '.');
      Debug.Assert(relativePathToLib.ToLower().EndsWith(Path.DirectorySeparatorChar + @"lib"));
 
      relativePathToLib += Path.DirectorySeparatorChar;
      m_RelativePathToLib = relativePathToLib;
   }
 
   private readonly string m_RelativePathToLib;
 
   internal Assembly AssemblyResolveHandler(object sender, ResolveEventArgs args) {
      Debug.Assert(args != null);
 
      var assemblyName = new AssemblyName(args.Name);
      Debug.Assert(assemblyName != null);
 
      var assemblyNameString = assemblyName.Name;
      Debug.Assert(!string.IsNullOrEmpty(assemblyNameString));
 
      // Special processing for NDepend.API and NDepend.Core because they are defined in $NDependInstallDir$\Lib
      if (assemblyNameString.ToLower() != "ndepend.api" &&
            assemblyNameString.ToLower() != "ndepend.core") {
         return null;
      }
 
      var location = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
      Debug.Assert(!string.IsNullOrEmpty(location));
 
      // Notice that the relative dirs "..\" and ".\" get resolved only when browsing the path, 
      // in the methods  File.Exists()  and  Assembly.LoadFrom() 
      // http://stackoverflow.com/a/6875932/27194
      var asmFilePath = Path.Combine(location, m_RelativePathToLib + assemblyName.Name + ".dll");
 
      if (!File.Exists(asmFilePath)) { return null; }
 
      var assembly = Assembly.LoadFrom(asmFilePath);
      return assembly;
   }
}