Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ All new detectors start as **IDefaultOffComponentDetector** (must be explicitly
1. **DefaultOff** → 2. **IExperimentalDetector** (enabled but output not captured) → 3. **Default** (fully integrated)

### Dependency Injection
All services auto-register via `ServiceCollectionExtensions.AddComponentDetection()` in Orchestrator. Detectors are discovered at runtime via `[Export]` attribute.
All services register via `ServiceCollectionExtensions.AddComponentDetection()` in Orchestrator using standard .NET DI. Detectors use constructor injection for dependencies.

## Creating a New Detector

Expand All @@ -37,9 +37,18 @@ All services auto-register via `ServiceCollectionExtensions.AddComponentDetectio

2. **Implement Detector**:
```csharp
[Export]
public class YourDetector : FileComponentDetector, IDefaultOffComponentDetector
{
public YourDetector(
IComponentStreamEnumerableFactory componentStreamEnumerableFactory,
IObservableDirectoryWalkerFactory walkerFactory,
ILogger<YourDetector> logger)
{
this.ComponentStreamEnumerableFactory = componentStreamEnumerableFactory;
this.Scanner = walkerFactory;
this.Logger = logger;
}

public override string Id => "YourEcosystem";
public override IEnumerable<string> Categories => [DetectorClass.YourCategory];
public override IEnumerable<ComponentType> SupportedComponentTypes => [ComponentType.YourType];
Expand All @@ -53,7 +62,13 @@ All services auto-register via `ServiceCollectionExtensions.AddComponentDetectio
}
```

3. **Register Components**:
3. **Register Detector in DI**:
Add to `ServiceCollectionExtensions.AddComponentDetection()` in Orchestrator:
```csharp
services.AddSingleton<IComponentDetector, YourDetector>();
```

4. **Register Components in Code**:
```csharp
var component = new DetectedComponent(new YourComponent("name", "1.0.0"));
recorder.RegisterUsage(
Expand Down
13 changes: 12 additions & 1 deletion docs/creating-a-new-detector.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ Go inside the project MS.VS.Services.Governance.ComponentDetection.Detectors, an

![ruby-component-detector.png](./images/creating-a-new-detector/ruby-component-detector.png)

- **Export**: All detectors are loaded during runtime, so this attribute helps with the discoverability.
- **Constructor Injection**: Detectors use standard .NET dependency injection. Inject required services like `IComponentStreamEnumerableFactory`, `IObservableDirectoryWalkerFactory`, and `ILogger<T>` through the constructor.
- **FileComponentDetector**: This is base class of all file based detectors
- **IDefaultOffComponentDetector**: All new detectors should also implement this interface (not shown in above screenshot) so new detectors are not going to run as part of the default set of detectors. They are only run when a user manually enables the detector via [argument](./enable-default-off.md).
- **Id**: Detector's identifier
Expand All @@ -81,6 +81,17 @@ Go inside the project MS.VS.Services.Governance.ComponentDetection.Detectors, an
### Advanced detection
The above example is a basic introduction to creating your own detector and should be sufficient for most new detectors. Sometimes you need more granularity when processing files, as such we have 2 additional detection [lifecycle methods](#-Detector-File-Processing-Lifecycle).

## Register the detector in dependency injection

After creating your detector class, you must register it in the dependency injection container. Add your detector to `src/Microsoft.ComponentDetection.Orchestrator/Extensions/ServiceCollectionExtensions.cs` in the `AddComponentDetection` method:

```csharp
// YourEcosystem
services.AddSingleton<IComponentDetector, YourDetector>();
```

This registration allows the orchestrator to discover and instantiate your detector at runtime.

## Registering Components

Each instance of a detector has a `ComponentRecorder`. A `ComponentRecorder` contains a set of `SingleFileComponentRecorder`s. A `SingleFileComponentRecorder` is an immutable graph store for components associated to a particular file. The purpose of any detector is to populate a given `SingleFileComponentRecorder` with a graph representation of all of the components found in a given file.
Expand Down
96 changes: 55 additions & 41 deletions docs/creating-a-new-service.md
Original file line number Diff line number Diff line change
@@ -1,73 +1,87 @@
# Creating a new service

We will be using [this PR](https://github.com/microsoft/component-detection/pull/12) following `EnvironmentVariableService` as a model for adding a new service.
The following steps are distilled and organized from that PR.
Component Detection uses standard .NET dependency injection for service registration. This guide shows how to add a new service to the system.

1. Create your new service interface in `src/Microsoft.ComponentDetection.Contracts/IMyNewService.cs`
2. Create your new service implementation in `src/Microsoft.ComponentDetection.Common/MyNewService.cs` implementing and exporting `IMyNewService`.
## Steps to create a new service

```c#
using System;
using System.Composition;
using Microsoft.ComponentDetection.Contracts;
1. **Create your service interface** in `src/Microsoft.ComponentDetection.Contracts/IMyNewService.cs`

namespace Microsoft.ComponentDetection.Common
```c#
namespace Microsoft.ComponentDetection.Contracts
{
[Export(typeof(IMyNewService))]
public class MyNewService : IMyNewService
public interface IMyNewService
{
...
// Define your service methods
string DoSomething();
}
}
```

3. Add your new service to `src/Microsoft.ComponentDetection.Contracts/IDetectorDependencies.cs`
2. **Create your service implementation** in `src/Microsoft.ComponentDetection.Common/MyNewService.cs`

```c#
namespace Microsoft.ComponentDetection.Contracts
using Microsoft.ComponentDetection.Contracts;

namespace Microsoft.ComponentDetection.Common
{
public interface IDetectorDependencies
public class MyNewService : IMyNewService
{
...
IMyNewService MyNewService { get; set; }
// Inject any dependencies your service needs
public MyNewService(ILogger<MyNewService> logger)
{
// Constructor injection
}

public string DoSomething()
{
// Implementation
}
}
}
```

4. Add your new service to `src/Microsoft.ComponentDetection.Common/DetectorDependencies.cs`
3. **Register your service** in `src/Microsoft.ComponentDetection.Orchestrator/Extensions/ServiceCollectionExtensions.cs`

Add your service registration to the `AddComponentDetection` method:

```c#
namespace Microsoft.ComponentDetection.Common
public static IServiceCollection AddComponentDetection(this IServiceCollection services)
{
[Export(typeof(IDetectorDependencies))]
public class DetectorDependencies : IDetectorDependencies
{
...
[Import]
public IMyNewService MyNewService { get; set; }
}
// ... existing registrations ...

// Your new service
services.AddSingleton<IMyNewService, MyNewService>();

// ... more registrations ...
return services;
}
```

5. Add your new service to `src/Microsoft.ComponentDetection.Contracts/Internal/InjectionParameters.cs`
4. **Use your service** in detectors or other services via constructor injection:

```c#
namespace Microsoft.ComponentDetection.Contracts.Internal
public class MyDetector : FileComponentDetector
{
internal class InjectionParameters
private readonly IMyNewService myNewService;

public MyDetector(
IComponentStreamEnumerableFactory componentStreamEnumerableFactory,
IObservableDirectoryWalkerFactory walkerFactory,
ILogger<MyDetector> logger,
IMyNewService myNewService) // Inject your service
{
internal InjectionParameters(IDetectorDependencies detectorDependencies)
{
...
myNewServiceStatic = detectorDependencies.MyNewService;
}
this.ComponentStreamEnumerableFactory = componentStreamEnumerableFactory;
this.Scanner = walkerFactory;
this.Logger = logger;
this.myNewService = myNewService;
}

...
private static IMyNewService myNewServiceStatic;

...
[Export(typeof(IMyNewService))]
public IMyNewService MyNewService => myNewServiceStatic;
}
```

## Service Lifetimes

- Use `AddSingleton` for stateless services or services that should be reused across the application lifetime
- Use `AddScoped` for services that should be created once per scan operation (rare in this codebase)
- Use `AddTransient` for lightweight, stateless services that should be created each time they're requested

Most services in Component Detection are registered as singletons.