Skip to content
Merged
24 changes: 2 additions & 22 deletions .github/workflows/ioc.benchmark.nativeaot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,8 @@ on:
workflow_dispatch:

push:
branches: [main]
paths:
- 'src/Ioc/src/SourceGen.Ioc/**'
- 'src/Ioc/src/SourceGen.Ioc.SourceGenerator/**'
- 'src/Ioc/test/SourceGen.Ioc.Benchmark/**'
- 'src/Directory.Build.props'
- 'Directory.Build.props'
- 'Directory.Packages.props'
- 'global.json'
- '.github/workflows/ioc.benchmark.nativeaot.yml'

pull_request:
branches: [main]
paths:
- 'src/Ioc/src/SourceGen.Ioc/**'
- 'src/Ioc/src/SourceGen.Ioc.SourceGenerator/**'
- 'src/Ioc/test/SourceGen.Ioc.Benchmark/**'
- 'src/Directory.Build.props'
- 'Directory.Build.props'
- 'Directory.Packages.props'
- 'global.json'
- '.github/workflows/ioc.benchmark.nativeaot.yml'
tags:
- 'ioc-v*'

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
Expand Down
26 changes: 25 additions & 1 deletion docs/Ioc/04_Field_Property_Method_Injection.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,32 @@ services.AddSingleton<global::MyNamespace.IMyService>((global::System.IServicePr

|ID|Severity|Description|
|:---|:---|:---|
|SGIOC007|Error|Invalid `[IocInject]` usage. The attribute cannot be applied to static members, inaccessible members (private setter, no setter, private field, readonly field, private method), or methods that do not return `void`.|
|SGIOC007|Error|Invalid `[IocInject]` usage. The attribute cannot be applied to static members, non-accessible members (`private`, `protected`, `private protected` — but `protected internal` is accepted), properties without a setter or with an inaccessible setter, `readonly` fields, generic methods, non-ordinary methods (e.g., constructors, operators), or methods that do not return `void`.|
|SGIOC022|Warning|`[IocInject]` is ignored when the corresponding feature (`PropertyInject`, `FieldInject`, or `MethodInject`) is disabled in `SourceGenIocFeatures`.|
|SGIOC023|Error|An element in the `InjectMembers` array is not in a recognized format. Each element must be `nameof(member)` or `new object[] { nameof(member), key [, KeyType] }`.|
|SGIOC024|Error|A member specified via `InjectMembers` is not injectable (e.g., static, non-accessible members (`private`, `protected`, `private protected` — but `protected internal` is accepted), no setter or inaccessible setter, `readonly` field, generic method, non-ordinary method, or method that does not return `void`).|

## InjectMembers: Attribute-Level Injection Without `[IocInject]`

When you cannot add `[IocInject]` directly to a type's members (e.g., a third-party type), use the `InjectMembers` property on `[IocRegisterFor]` to specify injection points from the registration site:

```csharp
// Register ThirdPartyService without modifying it
[IocRegisterFor(typeof(ThirdPartyService),
InjectMembers = [nameof(ThirdPartyService.Logger)])]
public static class ThirdPartyModule { }
```

Each element is one of:

| Format | Description |
| :--- | :--- |
| `nameof(T.Member)` | Inject without a key (resolves `T` from the container) |
| `new object[] { nameof(T.Member), "key" }` | Inject a keyed service |
| `new object[] { nameof(T.Member), nameof(SomeKey), KeyType.Csharp }` | Inject using a C# expression key |

> [!NOTE]
> When the same member is specified in both `InjectMembers` and via `[IocInject]` on the member itself, `[IocInject]` takes priority.

---

Expand Down
34 changes: 31 additions & 3 deletions docs/Ioc/08_Factory_Instance.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ services.AddScoped<global::TestNamespace.IHandler>((global::System.IServiceProvi
When working with open generic services, you can use a **generic factory method** to create instances. This requires the `[IocGenericFactory]` attribute to map service type placeholders to factory method type parameters.

> [!WARNING]
> If `Factory` points to a generic method but that method does not have `[IocGenericFactory]`, analyzer `SGIOC016` is reported and registration will not be generated.
> If `Factory` points to a generic method but that method does not have `[IocGenericFactory]` and no valid `GenericFactoryTypeMapping` is specified on the registration attribute, analyzer `SGIOC016` is reported and registration will not be generated. When using `GenericFactoryTypeMapping`, the number of placeholder types must match the factory method's type parameter count.

### Basic Generic Factory

Expand Down Expand Up @@ -409,15 +409,43 @@ services.AddSingleton<global::TestNamespace.IRequestHandler<global::System.Threa

</details>

## Generic Factory Without `[IocGenericFactory]`

As an alternative to placing `[IocGenericFactory]` on the factory method, you can specify the type mapping directly on the registration attribute via `GenericFactoryTypeMapping`. This is useful when you cannot modify the factory method (e.g., a third-party library).

```csharp
// No [IocGenericFactory] attribute needed on the factory method
[assembly: IocRegisterDefaults(
typeof(IRequestHandler<>),
ServiceLifetime.Singleton,
Factory = nameof(ExternalFactory.Create),
GenericFactoryTypeMapping = [typeof(IRequestHandler<Task<int>>), typeof(int)])]

public static class ExternalFactory
{
// Third-party or shared factory — cannot be modified
public static IRequestHandler<Task<T>> Create<T>()
=> throw new NotImplementedException();
}
```

The `GenericFactoryTypeMapping` property uses the same format as `[IocGenericFactory]`:

- **First element**: the service type template with concrete placeholder types (e.g., `typeof(IRequestHandler<Task<int>>)`)
- **Remaining elements**: the placeholder types that map to factory method type parameters in order

> [!NOTE]
> `GenericFactoryTypeMapping` is available on `[IocRegisterDefaults]` and `[IocRegisterFor]`. When both `GenericFactoryTypeMapping` on the attribute and `[IocGenericFactory]` on the method are present, `[IocGenericFactory]` takes precedence.

## Diagnostics

|ID|Severity|Description|
|:---|:---|:---|
|SGIOC008|Error|`Factory` or `Instance` uses `nameof()`, but the referenced field/property/method is not `static` or is inaccessible.|
|SGIOC009|Error|`Instance` is specified but `Lifetime` is not `Singleton`.|
|SGIOC010|Error|Both `Factory` and `Instance` are specified on the same attribute. `Factory` takes precedence.|
|SGIOC016|Error|Generic factory method does not have `[IocGenericFactory]` attribute.|
|SGIOC017|Error|`[IocGenericFactory]` placeholder types are duplicated. Each placeholder type must be unique.|
|SGIOC016|Error|Generic factory method does not have `[IocGenericFactory]` attribute and `GenericFactoryTypeMapping` is not specified on the registration attribute, or the placeholder count does not match the factory method's type parameters.|
|SGIOC017|Error|Placeholder types in `[IocGenericFactory]` or `GenericFactoryTypeMapping` are duplicated. Each placeholder type must be unique within its mapping.|

---

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#pragma checksum "C:\Users\andy0\source\repos\SourceGen\samples\Ioc\IocRazorSample\LoadData.razor" "{8829d00f-11b8-4213-878b-770e8597ac16}" "196e3337c0dc8c6624e8fdb4a1a1cc481a96d2dad5df79d4aab30dc2cd7bd57a"
#pragma checksum "c:\Users\andy0\source\repos\SourceGen\samples\Ioc\IocRazorSample\LoadData.razor" "{8829d00f-11b8-4213-878b-770e8597ac16}" "196e3337c0dc8c6624e8fdb4a1a1cc481a96d2dad5df79d4aab30dc2cd7bd57a"
// <auto-generated/>
#pragma warning disable 1591
namespace IocRazorSample
Expand All @@ -10,19 +10,19 @@ namespace IocRazorSample
using global::System.Threading.Tasks;
using global::Microsoft.AspNetCore.Components;
#nullable restore
#line (1,2)-(1,43) "C:\Users\andy0\source\repos\SourceGen\samples\Ioc\IocRazorSample\_Imports.razor"
#line (1,2)-(1,43) "c:\Users\andy0\source\repos\SourceGen\samples\Ioc\IocRazorSample\_Imports.razor"
using Microsoft.AspNetCore.Components.Web

#nullable disable
;
#nullable restore
#line (2,2)-(2,21) "C:\Users\andy0\source\repos\SourceGen\samples\Ioc\IocRazorSample\_Imports.razor"
#line (2,2)-(2,21) "c:\Users\andy0\source\repos\SourceGen\samples\Ioc\IocRazorSample\_Imports.razor"
using SourceGen.Ioc

#nullable disable
;
#nullable restore
#line (3,2)-(3,24) "C:\Users\andy0\source\repos\SourceGen\samples\Ioc\IocRazorSample\_Imports.razor"
#line (3,2)-(3,24) "c:\Users\andy0\source\repos\SourceGen\samples\Ioc\IocRazorSample\_Imports.razor"
using IocSample.Shared

#nullable disable
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#pragma checksum "C:\Users\andy0\source\repos\SourceGen\samples\Ioc\IocRazorSample\_Imports.razor" "{8829d00f-11b8-4213-878b-770e8597ac16}" "550f1173c09036256c057d57fd1381095e3d16eb6d45624996c5f140f0d8357e"
#pragma checksum "c:\Users\andy0\source\repos\SourceGen\samples\Ioc\IocRazorSample\_Imports.razor" "{8829d00f-11b8-4213-878b-770e8597ac16}" "550f1173c09036256c057d57fd1381095e3d16eb6d45624996c5f140f0d8357e"
// <auto-generated/>
#pragma warning disable 1591
namespace IocRazorSample
Expand All @@ -10,19 +10,19 @@ namespace IocRazorSample
using global::System.Threading.Tasks;
using global::Microsoft.AspNetCore.Components;
#nullable restore
#line (1,2)-(1,43) "C:\Users\andy0\source\repos\SourceGen\samples\Ioc\IocRazorSample\_Imports.razor"
#line (1,2)-(1,43) "c:\Users\andy0\source\repos\SourceGen\samples\Ioc\IocRazorSample\_Imports.razor"
using Microsoft.AspNetCore.Components.Web

#nullable disable
;
#nullable restore
#line (2,2)-(2,21) "C:\Users\andy0\source\repos\SourceGen\samples\Ioc\IocRazorSample\_Imports.razor"
#line (2,2)-(2,21) "c:\Users\andy0\source\repos\SourceGen\samples\Ioc\IocRazorSample\_Imports.razor"
using SourceGen.Ioc

#nullable disable
;
#nullable restore
#line (3,2)-(3,24) "C:\Users\andy0\source\repos\SourceGen\samples\Ioc\IocRazorSample\_Imports.razor"
#line (3,2)-(3,24) "c:\Users\andy0\source\repos\SourceGen\samples\Ioc\IocRazorSample\_Imports.razor"
using IocSample.Shared

#nullable disable
Expand Down
Loading