Source generator reference
Arc4u.Dependency.Tool contains two incremental source generators. This page describes exactly
what each one reads and emits. For the everyday setup, see
Dependency injection; for the architecture behind it, see the
design principles.
| Generator | Reads | Emits | Method |
|---|---|---|---|
DependencyToolGenerator |
The classes of the project marked [Export] |
Dependencies.g.cs |
Register<Name>Types |
GenerateRegisteredTypes |
Configs/appsettings.json (additional file) |
GeneratedTypes.g.cs |
RegisterTypes |
GenerateRegisteredTypes |
wwwroot/appsettings.json (additional file) |
GeneratedWwwRootTypes.g.cs |
RegisterWwwTypes |
Every method is a public static void extension method on IServiceCollection, declared in
public static partial class RegisterExtensions in the Arc4u.Dependency namespace. Add
using Arc4u.Dependency; to call it. The methods call Add..., never TryAdd...: calling a method
twice registers its services twice.
Note
On develop/9.0.0, each generated file starts with // <auto-generated/>, so the analyzers of
your project skip it. The published 9.0.0-preview37 package and earlier previews omit this
header: the analyzer rules of your project then also run on the generated files.
Configuration
The generators have no MSBuild options. DependencyToolGenerator needs no configuration.
GenerateRegisteredTypes needs the settings file declared as an additional file:
<ItemGroup>
<AdditionalFiles Include="Configs/appsettings.json" />
<AdditionalFiles Include="wwwroot/appsettings.json" />
</ItemGroup>
To keep the generated files on disk, set EmitCompilerGeneratedFiles to true. They are then
written to:
obj/<Configuration>/<TargetFramework>/generated/Arc4u.Dependency.Tool/
Arc4u.Dependency.Tool.DependencyToolGenerator/Dependencies.g.cs
Arc4u.Dependency.Tool.GenerateRegisteredTypes/GeneratedTypes.g.cs
Arc4u.Dependency.Tool.GenerateRegisteredTypes/GeneratedWwwRootTypes.g.cs
DependencyToolGenerator
Method name
The method is named Register<Name>Types, where <Name> is the last dot-separated segment of the
assembly name. Characters that are not letters, digits or _ are removed, and _ is added in
front of a name that starts with a digit.
| Assembly name | Method |
|---|---|
Contoso.Api |
RegisterApiTypes |
Contoso.Billing.Business |
RegisterBusinessTypes |
Contoso.Worker |
RegisterWorkerTypes |
The name is the layer of the project, not its full name, because a host references the layers of a
single microservice, where each layer exists once. Every generated method is an extension of
IServiceCollection in the Arc4u.Dependency namespace, so two referenced projects with the same
last segment make the call ambiguous (CS0121) (see
Register the services of several projects).
The method is generated even when the project has no [Export] class; it is then empty.
What is registered
The generator visits every class and record class declared in the project, public or not, including
nested classes. The attribute is resolved by its type, so [Export], [ExportAttribute],
[Arc4u.Dependency.Attribute.Export] and a using alias are all recognised. It writes one
registration for each [Export] attribute of a class, so a class exported for two contracts is
registered twice; [Scoped] and [Shared] apply to all of them. The lifetime is
Scoped when the class has [Scoped], otherwise Singleton when it has [Shared], otherwise
Transient. A class with both [Scoped] and [Shared] is registered as scoped, with warning
ARC4UDEP005. The registrations are independent: a [Shared] class exported for two contracts
has one instance per contract, not one shared instance.
[Export] arguments |
Generated call |
|---|---|
| none | services.Add{Lifetime}<TClass>() |
typeof(IService) |
services.Add{Lifetime}<IService, TClass>() |
"key" |
services.AddKeyed{Lifetime}<TClass>("key") |
"key", typeof(IService) |
services.AddKeyed{Lifetime}<IService, TClass>("key") |
An open generic class TClass<T> is registered with the Type overloads instead:
services.Add{Lifetime}(typeof(TClass<>)), services.Add{Lifetime}(typeof(IService<>), typeof(TClass<>))
or services.AddKeyed{Lifetime}(typeof(IService<>), "key", typeof(TClass<>)). The contract must be
open (typeof(IService<>)) and implemented by the class with its type parameters in the same order.
Any other generic combination, or a class nested in a generic class, is skipped with error
ARC4UDEP006.
For example, this project Contoso.Billing.Business:
using Arc4u.Dependency.Attribute;
namespace Contoso.Billing.Business;
public interface IPaymentGateway
{
string Name { get; }
}
public interface IRepository<T>
{
}
[Export("Stripe", typeof(IPaymentGateway)), Shared]
public class StripeGateway : IPaymentGateway
{
public string Name => "Stripe";
}
[Export(typeof(IRepository<string>))]
public class StringRepository : IRepository<string>
{
}
[Export, Scoped]
public class InvoiceNumberGenerator
{
}
produces this Dependencies.g.cs (9.0.0-preview37 and earlier write the same file without the
first line):
// <auto-generated/>
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace Arc4u.Dependency;
public static partial class RegisterExtensions
{
public static void RegisterBusinessTypes(this IServiceCollection services)
{
services.AddKeyedSingleton<Contoso.Billing.Business.IPaymentGateway, global::Contoso.Billing.Business.StripeGateway>("Stripe");
services.AddTransient<Contoso.Billing.Business.IRepository<string>, global::Contoso.Billing.Business.StringRepository>();
services.AddScoped<global::Contoso.Billing.Business.InvoiceNumberGenerator>();
}
}
GenerateRegisteredTypes
Which file is read
The generator reads an additional file whose path, relative to the project, is exactly
Configs/appsettings.json or wwwroot/appsettings.json (case-insensitive). The project folder is
the ProjectDir MSBuild property, which every project using the .NET SDK sets. When the property
is missing, the generator falls back to the shortest folder path that contains a .cs file of the
compilation, and reports warning ARC4UDEP007 if there is none. Other files named
appsettings.json are ignored, even when declared as additional files.
The file is parsed with System.Text.Json, accepting comments and trailing commas. Property names
are case-sensitive. If the file cannot be read, the build reports error ARC4UDEP001 at the
offending line and the method is generated empty, so that its callers still compile. If the file
has no Application.Dependency section, the method is generated empty.
Entry format
{
"Application.Dependency": {
"RegisterTypes": [
"Arc4u.Caching.Memory.MemoryCache, Arc4u.Caching.Memory"
]
}
}
Each entry of RegisterTypes is a string Namespace.Type, AssemblyName:
AssemblyNameis matched against the names of the assemblies the project references. Package references and project references both work.Version=is optional. When present, it is compared with the assembly version of the reference (Version=9.0.0.0matches the assembly of package9.0.0-preview37); only the parts given are compared, soVersion=9.0matches9.0.1.0. A reference whose path contains the version string is also accepted.- The assembly is read as metadata, without being loaded. The type must be public, since the generated code references it, and must not be nested in another type: nested types are skipped.
- The registration follows the
[Export],[Shared]and[Scoped]attributes of the type, with the same table asDependencyToolGenerator: a type with both[Shared]and[Scoped]is registered as scoped, with warningARC4UDEP005. A generic type cannot be listed: it is skipped with errorARC4UDEP006. - An entry that cannot be registered is skipped with a warning that points at it in
appsettings.json:ARC4UDEP002for a malformed entry,ARC4UDEP003when the assembly is not referenced or has another version,ARC4UDEP004when the type is not found or has no[Export].
The generated method groups the registrations by assembly:
public static void RegisterTypes(this IServiceCollection services)
{
// Types registered by nuget package: Arc4u.Caching.Memory
services.AddKeyedTransient<Arc4u.Caching.ICache, Arc4u.Caching.Memory.MemoryCache>("Memory");
}
The 8.x keys Assemblies and RejectedTypes are ignored.
Troubleshooting
See Troubleshooting in the dependency injection guide.
See also
- Dependency injection
- Source generators overview
- ExportAttribute, SharedAttribute and ScopedAttribute in the API reference