|
| 1 | +# Copilot Instructions for nanoFramework.DependencyInjection |
| 2 | + |
| 3 | +## Project Overview |
| 4 | + |
| 5 | +This repository implements a **Dependency Injection (DI) / Inversion of Control (IoC) container** for [.NET nanoFramework](https://www.nanoframework.net/) — a free, open-source platform that enables C# development for constrained embedded devices (microcontrollers). The library mirrors the API of the official [Microsoft.Extensions.DependencyInjection](https://docs.microsoft.com/en-us/dotnet/core/extensions/dependency-injection) package as closely as possible, with adaptations for the limitations of nanoFramework. |
| 6 | + |
| 7 | +The NuGet package is published as **`nanoFramework.DependencyInjection`** and is a dependency of [nanoFramework.Hosting](https://github.com/nanoframework/nanoFramework.Hosting). |
| 8 | + |
| 9 | +## Repository Structure |
| 10 | + |
| 11 | +``` |
| 12 | +/ |
| 13 | +├── nanoFramework.DependencyInjection/ # Main library project |
| 14 | +│ ├── nanoFramework.DependencyInjection.nfproj # nanoFramework project file (not .csproj) |
| 15 | +│ ├── Microsoft/Extensions/DependencyInjection/ # Core DI types (mirrors MS.Extensions.DI namespace) |
| 16 | +│ │ ├── ServiceCollection.cs |
| 17 | +│ │ ├── ServiceDescriptor.cs |
| 18 | +│ │ ├── ServiceLifetime.cs |
| 19 | +│ │ ├── ServiceProvider.cs |
| 20 | +│ │ ├── ServiceProviderEngine.cs |
| 21 | +│ │ ├── ServiceProviderEngineScope.cs |
| 22 | +│ │ ├── ServiceProviderOptions.cs |
| 23 | +│ │ ├── ActivatorUtilities.cs |
| 24 | +│ │ ├── ServiceCollectionServiceExtensions.cs |
| 25 | +│ │ ├── ServiceCollectionDescriptorExtensions.cs |
| 26 | +│ │ ├── ServiceCollectionContainerBuilderExtensions.cs |
| 27 | +│ │ ├── ServiceProviderServiceExtensions.cs |
| 28 | +│ │ ├── ServiceScope.cs |
| 29 | +│ │ ├── TypeExtensions.cs |
| 30 | +│ │ └── (interfaces: IServiceCollection, IServiceScope, IServiceProviderIsService) |
| 31 | +│ └── System/ # nanoFramework-specific polyfills |
| 32 | +│ ├── Activator.cs # Custom Activator (uses reflection) |
| 33 | +│ ├── AggregateException.cs # Custom AggregateException |
| 34 | +│ └── IServiceProvider.cs |
| 35 | +├── tests/ # Unit tests |
| 36 | +│ ├── nanoFramework.DependencyInjection.UnitTests.nfproj |
| 37 | +│ ├── nano.runsettings # Test run settings (IsRealHardware=False → nanoCLR simulator) |
| 38 | +│ ├── Fakes/ # Test fakes and stubs |
| 39 | +│ ├── DependencyInjectionTests.cs |
| 40 | +│ ├── ServiceProviderTests.cs |
| 41 | +│ ├── ServiceCollectionTests.cs |
| 42 | +│ ├── ServiceCollectionDescriptorExtensionsTests.cs |
| 43 | +│ ├── ServiceProviderExtensionsTests.cs |
| 44 | +│ ├── ActivatorTests.cs |
| 45 | +│ ├── ActivatorUtilitiesTests.cs |
| 46 | +│ └── AggregateExceptionTests.cs |
| 47 | +├── nanoFramework.DependencyInjection.sln # Visual Studio solution |
| 48 | +├── nanoFramework.DependencyInjection.nuspec # NuGet package specification |
| 49 | +├── azure-pipelines.yml # Primary CI/CD (Azure Pipelines) |
| 50 | +├── version.json # Nerdbank.GitVersioning config (version: 1.1) |
| 51 | +├── NuGet.Config # NuGet source (nuget.org only) |
| 52 | +└── .github/workflows/ # GitHub Actions (PR checks, dependency updates) |
| 53 | + ├── pr-checks.yml # Checks package lock and NuGet versions on PRs |
| 54 | + ├── update-dependencies.yml |
| 55 | + ├── update-dependencies-develop.yml |
| 56 | + └── generate-changelog.yml |
| 57 | +``` |
| 58 | + |
| 59 | +## Critical Platform Constraints |
| 60 | + |
| 61 | +**.NET nanoFramework is NOT standard .NET.** The code targets an embedded runtime with significant restrictions: |
| 62 | + |
| 63 | +1. **No generics in the public API** — All methods that would use `<T>` in standard .NET use `Type` parameters instead. Example: `serviceCollection.AddSingleton(typeof(IMyService), typeof(MyService))` instead of `AddSingleton<IMyService, MyService>()`. |
| 64 | + |
| 65 | +2. **Limited reflection** — `Type.GetConstructor()`, `Type.GetConstructors()`, `ConstructorInfo.Invoke()` are available but behaviour can differ from full .NET. `GetType()` is available on instances. |
| 66 | + |
| 67 | +3. **No struct support in DI container** — `struct` types cannot be used as service registrations. The constructor for `struct` types fails in `typeof(T).GetConstructor()`. See the commented-out test `ServicesRegisteredWithImplementationTypeForStructSingletonServices` and the issue reference: https://github.com/nanoframework/Home/issues/1085. |
| 68 | + |
| 69 | +4. **No array-typed constructor parameters** — Types that have `byte[]` or other array parameters in their constructors will cause a null exception during activation. |
| 70 | + |
| 71 | +5. **No full BCL** — Only a subset of the .NET base class library is available via `nanoFramework.CoreLibrary` (`mscorlib`). Standard types like `ArrayList` (not `List<T>`), `Hashtable`, etc. are used. |
| 72 | + |
| 73 | +6. **Namespace `System.Collections`** — Use `ArrayList` and `Hashtable` instead of generic collections. |
| 74 | + |
| 75 | +7. **Factory delegates use a custom delegate** — `ImplementationFactoryDelegate` (`delegate object ImplementationFactoryDelegate(IServiceProvider serviceProvider)`) is used instead of `Func<IServiceProvider, object>`. |
| 76 | + |
| 77 | +## Build Environment |
| 78 | + |
| 79 | +> **⚠️ IMPORTANT: The nanoFramework build system is Windows-only.** |
| 80 | +
|
| 81 | +The project uses `.nfproj` files (nanoFramework project format), which require: |
| 82 | +- **Windows OS** with the nanoFramework Visual Studio extension or MSBuild extensions installed at `$(MSBuildExtensionsPath)\nanoFramework\v1.0\` |
| 83 | +- **MSBuild** (via Visual Studio or .NET SDK for Windows) |
| 84 | +- The CI/CD pipeline (`azure-pipelines.yml`) uses `windows-latest` runners |
| 85 | + |
| 86 | +**You cannot build or run tests in a Linux/macOS environment** (the standard GitHub Copilot cloud agent environment). Attempting `dotnet build` on `.nfproj` files will fail because the `NFProjectSystem.props`/`.targets` files are not present. |
| 87 | + |
| 88 | +### What you CAN do in the cloud agent environment: |
| 89 | +- Read, analyze, and edit all source files |
| 90 | +- Update documentation (README.md, CHANGELOG.md, XML doc comments) |
| 91 | +- Add/modify test files and source files |
| 92 | +- Review and update NuGet package versions in `packages.config` |
| 93 | +- Update GitHub Actions workflows in `.github/workflows/` |
| 94 | +- Make structural changes that will be validated by CI on Azure Pipelines |
| 95 | + |
| 96 | +### What requires Windows to validate: |
| 97 | +- Compiling the project |
| 98 | +- Running unit tests (via nanoCLR simulator with `nanoFramework.TestFramework`) |
| 99 | +- Verifying NuGet package restore |
| 100 | + |
| 101 | +## Testing |
| 102 | + |
| 103 | +Tests use **nanoFramework.TestFramework** (not MSTest, NUnit, or xUnit). Key points: |
| 104 | + |
| 105 | +- Test classes are decorated with `[TestClass]` and methods with `[TestMethod]` from `nanoFramework.TestFramework` |
| 106 | +- Assertions use `Assert.IsType()`, `Assert.AreEqual()`, `Assert.AreNotSame()`, `Assert.IsNull()`, `Assert.IsNotNull()`, `Assert.ThrowsException()`, etc. |
| 107 | +- Tests run on the **nanoCLR simulator** (not real hardware) — configured in `tests/nano.runsettings` via `<IsRealHardware>False</IsRealHardware>` |
| 108 | +- Test timeout: 120 seconds (`<TestSessionTimeout>120000</TestSessionTimeout>`) |
| 109 | +- The Azure Pipelines build passes `unitTestRunsettings: '$(System.DefaultWorkingDirectory)\tests\nano.runsettings'` to the test runner |
| 110 | + |
| 111 | +## Service Lifetimes |
| 112 | + |
| 113 | +The library supports three service lifetimes (same as standard .NET): |
| 114 | + |
| 115 | +| Lifetime | Behavior | |
| 116 | +|----------|----------| |
| 117 | +| `Singleton` | One instance shared across the entire application lifetime | |
| 118 | +| `Transient` | A new instance created every time the service is requested | |
| 119 | +| `Scoped` | One instance per scope (created via `serviceProvider.CreateScope()`) | |
| 120 | + |
| 121 | +## Key API Patterns |
| 122 | + |
| 123 | +```csharp |
| 124 | +// Registration (no generics — use Type parameters) |
| 125 | +var serviceProvider = new ServiceCollection() |
| 126 | + .AddSingleton(typeof(IMyService), typeof(MyServiceImpl)) |
| 127 | + .AddTransient(typeof(IAnotherService), typeof(AnotherServiceImpl)) |
| 128 | + .AddScoped(typeof(IScopedService), typeof(ScopedServiceImpl)) |
| 129 | + .BuildServiceProvider(); |
| 130 | + |
| 131 | +// Resolution |
| 132 | +var service = (IMyService)serviceProvider.GetService(typeof(IMyService)); |
| 133 | +var required = (IMyService)serviceProvider.GetRequiredService(typeof(IMyService)); |
| 134 | +object[] services = serviceProvider.GetServices(typeof(IMyService)); |
| 135 | + |
| 136 | +// Scoped resolution |
| 137 | +using (var scope = serviceProvider.CreateScope()) |
| 138 | +{ |
| 139 | + var scoped = scope.ServiceProvider.GetService(typeof(IScopedService)); |
| 140 | +} |
| 141 | + |
| 142 | +// Factory registration |
| 143 | +services.AddSingleton(typeof(IMyService), (IServiceProvider sp) => new MyServiceImpl()); |
| 144 | + |
| 145 | +// Instance registration |
| 146 | +services.AddSingleton(typeof(IMyService), existingInstance); |
| 147 | + |
| 148 | +// ActivatorUtilities (create without registering) |
| 149 | +var instance = (MyClass)ActivatorUtilities.CreateInstance(provider, typeof(MyClass), arg1, arg2); |
| 150 | + |
| 151 | +// Validation options |
| 152 | +var provider = services.BuildServiceProvider(new ServiceProviderOptions |
| 153 | +{ |
| 154 | + ValidateOnBuild = true, // Validate all services can be constructed |
| 155 | + ValidateScopes = true // Validate scoped services never resolved from root |
| 156 | +}); |
| 157 | +``` |
| 158 | + |
| 159 | +## NuGet Package Management |
| 160 | + |
| 161 | +This project uses the **old-style `packages.config`** NuGet format (not `PackageReference`). To update packages: |
| 162 | +- Edit `packages.config` in the relevant project directory |
| 163 | +- Edit the corresponding `<HintPath>` references in the `.nfproj` file |
| 164 | +- The lock file (`packages.lock.json`) is enforced in CI via `<RestoreLockedMode>true</RestoreLockedMode>` |
| 165 | + |
| 166 | +Main dependencies: |
| 167 | +- `nanoFramework.CoreLibrary` (v1.17.11) — the nanoFramework mscorlib |
| 168 | +- `Nerdbank.GitVersioning` (v3.9.50) — automatic versioning from git history |
| 169 | +- `nanoFramework.TestFramework` (v3.0.77) — test framework (tests project only) |
| 170 | + |
| 171 | +## Versioning |
| 172 | + |
| 173 | +Versioning is managed by **Nerdbank.GitVersioning** via `version.json`. The current version base is `1.1`. Releases are tagged `v*` and trigger publish jobs in the Azure Pipelines CI. Preview packages are published from non-release branches. |
| 174 | + |
| 175 | +## Code Style |
| 176 | + |
| 177 | +- File header: `// Copyright (c) .NET Foundation and Contributors\n// See LICENSE file in the project root for full license information.` |
| 178 | +- Namespace: Main library uses `Microsoft.Extensions.DependencyInjection` and `System` (polyfills); tests use `nanoFramework.DependencyInjection.UnitTests` |
| 179 | +- XML doc comments on all public API members |
| 180 | +- `lock (_syncLock)` pattern used in `ServiceCollection` for thread safety |
| 181 | +- `.editorconfig` only sets `dotnet_analyzer_diagnostic.severity = silent` |
| 182 | +- Assembly is strong-named via `key.snk` |
| 183 | + |
| 184 | +## Known Issues and Workarounds |
| 185 | + |
| 186 | +1. **Struct types in DI**: `struct` types cannot be registered or resolved. This is a nanoFramework limitation (issue [#1085](https://github.com/nanoframework/Home/issues/1085)). The affected test is commented out. |
| 187 | + |
| 188 | +2. **Array constructor parameters**: Types with array-typed constructor parameters (e.g., `byte[]`) will throw a null exception during activation. Document this limitation in any affected code/tests. |
| 189 | + |
| 190 | +3. **`GetImplementationType()` with factory**: When a service is registered only via a factory (`ImplementationFactory`), `GetImplementationType()` returns `null` because nanoFramework lacks generic type argument introspection. See the commented-out code in `ServiceDescriptor.cs`. |
| 191 | + |
| 192 | +4. **`RestoreLockedMode` in CI**: The `packages.lock.json` is enforced in CI. If you update package versions in `packages.config`, the lock file must be regenerated on a Windows machine before the CI will pass. |
| 193 | + |
| 194 | +5. **Building in cloud agent**: As noted above, building `.nfproj` files requires the nanoFramework project system on Windows. Code changes should be validated by the Azure Pipelines CI after merging or creating a PR. |
| 195 | + |
| 196 | +## CI/CD |
| 197 | + |
| 198 | +- **Primary CI**: Azure Pipelines (`azure-pipelines.yml`) — builds, runs tests, publishes NuGet packages |
| 199 | +- **PR Checks** (GitHub Actions): `.github/workflows/pr-checks.yml` — verifies `packages.lock.json` consistency and that NuGet packages are up to date |
| 200 | +- **Dependency updates**: Automated via `.github/workflows/update-dependencies.yml` and `update-dependencies-develop.yml` |
| 201 | +- **Downstream dependents**: `nanoFramework.Hosting` is automatically updated when a new release is published |
| 202 | + |
| 203 | +## Related Resources |
| 204 | + |
| 205 | +- [nanoFramework Home](https://github.com/nanoframework/Home) |
| 206 | +- [nanoFramework.Hosting](https://github.com/nanoframework/nanoFramework.Hosting) — uses this library |
| 207 | +- [DependencyInjection Samples](https://github.com/nanoframework/Samples/tree/main/samples/DependencyInjection) |
| 208 | +- [Contributing Guide](https://github.com/nanoframework/Home/blob/main/CONTRIBUTING.md) |
| 209 | +- [Discord Community](https://discord.gg/gCyBu8T) |
0 commit comments