From ee2448282eee966390ced8935f56ab0dbe427ac5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 10 Apr 2026 08:12:08 +0000 Subject: [PATCH] Add ASP.NET Core controller registration tests and documentation Agent-Logs-Url: https://github.com/ffMathy/ServiceScan.SourceGenerator/sessions/da938bc9-00e3-4e11-8330-ee1b1dd0086b Co-authored-by: ffMathy <2824010+ffMathy@users.noreply.github.com> --- README.md | 34 +++++ .../AddServicesTests.cs | 117 ++++++++++++++++++ 2 files changed, 151 insertions(+) diff --git a/README.md b/README.md index e0e6dd6..560907e 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,40 @@ It adds MediatR requests handlers, although you might need to add other types li public static partial IServiceCollection AddRepositories(this IServiceCollection services); ``` +### Register ASP.NET Core controllers as services +By default, ASP.NET Core controllers are not registered in the DI container — they are instantiated using an activator. However, if you want controllers to be resolved from DI (equivalent to calling `AddControllersAsServices()`), you can register them with `ServiceScan`: +```csharp +[GenerateServiceRegistrations( + AssignableTo = typeof(ControllerBase), + AsSelf = true, + Lifetime = ServiceLifetime.Transient)] +public static partial IServiceCollection AddControllersAsServices(this IServiceCollection services); +``` +This discovers all classes inheriting from `ControllerBase` and registers each one as a transient service with its concrete type. You can then combine this with `services.AddControllers()`: +```csharp +services.AddControllers(); +services.AddControllersAsServices(); +``` +This gives you full DI control over controllers — allowing lifetime customization, decoration, or replacement of individual controllers. + +You can also use `TypeNameFilter` to discover controllers by naming convention instead: +```csharp +[GenerateServiceRegistrations( + TypeNameFilter = "*Controller", + AsSelf = true, + Lifetime = ServiceLifetime.Transient)] +public static partial IServiceCollection AddControllersAsServices(this IServiceCollection services); +``` + +And you can exclude specific controllers using `ExcludeByTypeName`: +```csharp +[GenerateServiceRegistrations( + AssignableTo = typeof(ControllerBase), + AsSelf = true, + ExcludeByTypeName = "*InternalController")] +public static partial IServiceCollection AddControllersAsServices(this IServiceCollection services); +``` + ### Add AspNetCore Minimal API endpoints You can add custom type handler, if you need to do something non-trivial with that type. For example, you can automatically discover and map Minimal API endpoints: diff --git a/ServiceScan.SourceGenerator.Tests/AddServicesTests.cs b/ServiceScan.SourceGenerator.Tests/AddServicesTests.cs index af7aa1c..24f5c81 100644 --- a/ServiceScan.SourceGenerator.Tests/AddServicesTests.cs +++ b/ServiceScan.SourceGenerator.Tests/AddServicesTests.cs @@ -1072,6 +1072,123 @@ public class MyService2 : IService Assert.Equal(Sources.GetMethodImplementation(registrations), results.GeneratedTrees[2].ToString()); } + [Fact] + public void AddAspNetCoreControllersAsServices() + { + var attribute = """[GenerateServiceRegistrations(AssignableTo = typeof(ControllerBase), AsSelf = true, Lifetime = ServiceLifetime.Transient)]"""; + + var compilation = CreateCompilation( + Sources.MethodWithAttribute(attribute), + """ + namespace GeneratorTests; + + public abstract class ControllerBase { } + public abstract class Controller : ControllerBase { } + public class HomeController : Controller { } + public class AccountController : Controller { } + """); + + var results = CSharpGeneratorDriver + .Create(_generator) + .RunGenerators(compilation) + .GetRunResult(); + + var registrations = $""" + return services + .AddTransient() + .AddTransient(); + """; + Assert.Equal(Sources.GetMethodImplementation(registrations), results.GeneratedTrees[2].ToString()); + } + + [Fact] + public void AddAspNetCoreControllersAsServicesWithTypeNameFilter() + { + var attribute = """[GenerateServiceRegistrations(TypeNameFilter = "*Controller", AsSelf = true, Lifetime = ServiceLifetime.Transient)]"""; + + var compilation = CreateCompilation( + Sources.MethodWithAttribute(attribute), + """ + namespace GeneratorTests; + + public abstract class ControllerBase { } + public abstract class Controller : ControllerBase { } + public class HomeController : Controller { } + public class AccountController : Controller { } + public class NotAControllerService { } + """); + + var results = CSharpGeneratorDriver + .Create(_generator) + .RunGenerators(compilation) + .GetRunResult(); + + var registrations = $""" + return services + .AddTransient() + .AddTransient(); + """; + Assert.Equal(Sources.GetMethodImplementation(registrations), results.GeneratedTrees[2].ToString()); + } + + [Fact] + public void AddAspNetCoreControllersAsScopedServices() + { + var attribute = """[GenerateServiceRegistrations(AssignableTo = typeof(ControllerBase), AsSelf = true, Lifetime = ServiceLifetime.Scoped)]"""; + + var compilation = CreateCompilation( + Sources.MethodWithAttribute(attribute), + """ + namespace GeneratorTests; + + public abstract class ControllerBase { } + public abstract class Controller : ControllerBase { } + public class HomeController : Controller { } + public class AccountController : Controller { } + """); + + var results = CSharpGeneratorDriver + .Create(_generator) + .RunGenerators(compilation) + .GetRunResult(); + + var registrations = $""" + return services + .AddScoped() + .AddScoped(); + """; + Assert.Equal(Sources.GetMethodImplementation(registrations), results.GeneratedTrees[2].ToString()); + } + + [Fact] + public void AddAspNetCoreControllersWithExclusion() + { + var attribute = """[GenerateServiceRegistrations(AssignableTo = typeof(ControllerBase), AsSelf = true, ExcludeByTypeName = "*Account*")]"""; + + var compilation = CreateCompilation( + Sources.MethodWithAttribute(attribute), + """ + namespace GeneratorTests; + + public abstract class ControllerBase { } + public abstract class Controller : ControllerBase { } + public class HomeController : Controller { } + public class AccountController : Controller { } + public class AccountSettingsController : Controller { } + """); + + var results = CSharpGeneratorDriver + .Create(_generator) + .RunGenerators(compilation) + .GetRunResult(); + + var registrations = $""" + return services + .AddTransient(); + """; + Assert.Equal(Sources.GetMethodImplementation(registrations), results.GeneratedTrees[2].ToString()); + } + [Fact] public void DontGenerateAnythingIfTypeIsInvalid() {