Roslyn-based static analysis tool for .NET microservice architectures. Scans source code to find entry points, trace message flows between services, and resolve DI dependency chains — without building or running the projects.
In a microservice architecture with dozens of repositories, answering simple questions becomes painful:
- "Where is this request handled?" — grep across 100+ repos, hope the naming is consistent
- "Who listens to this message?" — search every repo for the message type, miss half of them
- "What does this handler depend on?" — open the file, trace each injected service manually, repeat 3 levels deep
- "Which messages have no consumers?" — nobody knows until something breaks in production
EntryFinder answers all of these by parsing C# source code with Roslyn and building a structured index of entry points, message flows, and dependency trees.
dotnet tool install -g EntryFinderOr clone and build:
git clone <repo-url>
cd EntryFinder
dotnet build# List all entry points (handlers, controllers, message handlers) in a repo
easyfinder scan ~/src/orders
# Search for entry points by name, request/response type, or attribute
easyfinder find CreateOrder ~/src/orders
# Build a cross-service message flow graph
easyfinder graph ~/src
# Trace who produces and consumes a specific message
easyfinder trace OrderCreatedMessage ~/src
# Show the DI dependency tree of a class (default depth: 3)
easyfinder deps CreateOrderHandler ~/src/orders
# Any command with --mermaid outputs a Mermaid diagram
easyfinder graph ~/src --mermaid
easyfinder trace OrderCreatedMessage ~/src --mermaid
easyfinder deps CreateOrderHandler ~/src/orders --mermaidFound 48 entry points in ~/src/orders
[Handler] (18)
CreateOrderHandler <CreateOrderRequest, CreateOrderResponse>
API/Orders/CreateOrderHandler.cs:14
GetOrderHandler <GetOrderRequest, GetOrderResponse>
API/Orders/GetOrderHandler.cs:11
...
[MessageHandler] (22)
PaymentCompletedHandler <PaymentCompletedMessage> [MessageHandler(Queue.Payments, RetryCount = 5)]
MessageHandlers/PaymentCompletedHandler.cs:16
...
[Controller] (8)
HealthController
Controllers/HealthController.cs:7
...
OrderCreatedMessage
Producers:
orders/CreateOrderHandler API/Orders/CreateOrderHandler.cs:42
Consumers:
notifications/SendOrderConfirmationHandler queue=Queue.Orders
MessageHandlers/SendOrderConfirmationHandler.cs:11
warehouse/ReserveStockHandler queue=Queue.Orders
MessageHandlers/ReserveStockHandler.cs:18
[Handler] CreateOrderHandler
API/Orders/CreateOrderHandler.cs:14
+ IOrderRepository orderRepository -> DAL/OrderRepository.cs
- IDbMapper dbMapper
+ IPricingService pricingService -> Logic/PricingService.cs
+ IDiscountEngine discountEngine -> Logic/DiscountEngine.cs
- ICache cache
+ IPaymentGateway paymentGateway -> Logic/PaymentGateway.cs
- IHttpClientFactory httpClientFactory
- ILogger logger
- IPublisher publisher
Output can be pasted into any Markdown renderer that supports Mermaid (GitHub, Confluence, Notion):
graph LR
S0["<b>orders</b><br/>48 entries"]
S1["<b>notifications</b><br/>12 entries"]
S2["<b>warehouse</b><br/>31 entries"]
S3["<b>payments</b><br/>24 entries"]
S0 -->|"OrderCreatedMessage"| S1
S0 -->|"OrderCreatedMessage"| S2
S3 -->|"PaymentCompletedMessage"| S0
S0 -->|"StockReservedMessage"| S2
Finds classes that inherit from configurable base types. Default patterns:
| Kind | Base Types | Description |
|---|---|---|
| Handler | RequestHandler, InternalRequestHandler |
CQRS-style request handlers |
| MessageHandler | MessageHandler |
Async message/event handlers |
| Controller | Controller, ControllerBase, ApiController |
ASP.NET MVC/API controllers |
| GrpcService | GrpcService |
gRPC service implementations |
For each entry point, extracts:
- Class name and generic type arguments (request/response types)
- Attributes (e.g.,
[MessageHandler(Queue.Orders, RetryCount = 5)]) - File path and line number
Builds a cross-service message graph by detecting:
- Producers — calls to
PublishAsync(new SomeMessage { ... })orPublish<T>(...) - Consumers — classes inheriting from
MessageHandler<T>or similar base types - Orphan messages — messages with no producers or no consumers in the scanned codebase
Resolves constructor injection chains using Roslyn syntax analysis:
- Primary constructors (C# 12):
class MyHandler(IFoo foo) : BaseHandler - Classic constructors:
public MyHandler(IFoo foo) { _foo = foo; } - Interface resolution:
IFoo→ findsFooclass, or any class implementingIFoo - Configurable depth: default 3 levels, avoids infinite recursion
All graph-producing commands support --mermaid flag:
graph --mermaid— service-to-service message flow diagramtrace --mermaid— producer/consumer diagram for a specific message (colored: green=producer, yellow=message, purple=consumer)deps --mermaid— DI dependency tree (colored: blue=root, green=resolved, gray=infrastructure)
Create a config.json next to the executable or in the working directory:
{
"defaultRoot": "~/src",
"patterns": [
{
"kind": "Handler",
"baseTypes": ["RequestHandler", "InternalRequestHandler"]
},
{
"kind": "MessageHandler",
"baseTypes": ["MessageHandler"]
},
{
"kind": "Controller",
"baseTypes": ["Controller", "ControllerBase"]
},
{
"kind": "Job",
"baseTypes": ["BackgroundJob", "RecurringJob"]
}
]
}Patterns are fully configurable — add your own base types for any framework (MediatR, MassTransit, NServiceBus, custom handlers).
- No build required — parses source code with
CSharpSyntaxTree.ParseText(), no need to compile or resolve NuGet packages - No reflection — everything is syntax-level analysis
- Service detection — each subdirectory containing
.csprojfiles is treated as a service - Interface resolution — tries
IFoo→Fooconvention first, then scans for classes implementing the interface - File filtering — skips
bin/,obj/,.git/, and test projects by default
- Syntax-only analysis — does not resolve namespaces or NuGet packages. Two classes with the same name in different namespaces will collide (first one wins)
- Interface resolution is heuristic — relies on naming conventions and base list scanning. DI registration modules are not parsed
- Message detection — only detects
PublishAsync(new T { })andPublish<T>()patterns. Dynamic dispatch is not detected - No incremental scanning — every run parses all files from scratch
- Index caching — save scan results to disk, only re-parse changed files
- HTTP route mapping — link handlers to their actual URL endpoints
- SQL table detection — trace from handler → repository → SQL queries → table names
- Watch mode — file watcher that incrementally updates the index
- JSON export — machine-readable output for CI/CD integration
MIT