{"id":23283546,"url":"https://github.com/devlooped/dependencyinjection","last_synced_at":"2025-08-21T15:33:31.770Z","repository":{"id":60222097,"uuid":"540306941","full_name":"devlooped/DependencyInjection","owner":"devlooped","description":"Provides compile-time discovery and code generation of service registrations from attributed types and conventions ","archived":false,"fork":false,"pushed_at":"2025-08-14T00:31:22.000Z","size":305,"stargazers_count":59,"open_issues_count":8,"forks_count":4,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-08-15T15:07:34.480Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/devlooped.png","metadata":{"files":{"readme":"readme.md","changelog":"changelog.md","contributing":null,"funding":null,"license":"license.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":"devlooped"}},"created_at":"2022-09-23T06:23:05.000Z","updated_at":"2025-06-05T01:27:53.000Z","dependencies_parsed_at":"2024-03-22T19:52:36.849Z","dependency_job_id":"0b2ff5ae-df2c-439b-a471-6d33f8d6be1d","html_url":"https://github.com/devlooped/DependencyInjection","commit_stats":null,"previous_names":["devlooped/dependencyinjection"],"tags_count":23,"template":false,"template_full_name":null,"purl":"pkg:github/devlooped/DependencyInjection","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devlooped%2FDependencyInjection","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devlooped%2FDependencyInjection/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devlooped%2FDependencyInjection/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devlooped%2FDependencyInjection/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/devlooped","download_url":"https://codeload.github.com/devlooped/DependencyInjection/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devlooped%2FDependencyInjection/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271500361,"owners_count":24770375,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-21T02:00:08.990Z","response_time":74,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-12-20T01:16:45.120Z","updated_at":"2025-08-21T15:33:31.752Z","avatar_url":"https://github.com/devlooped.png","language":"C#","funding_links":["https://github.com/sponsors/devlooped","https://github.com/sponsors"],"categories":[],"sub_categories":[],"readme":"![Icon](https://raw.githubusercontent.com/devlooped/DependencyInjection/main/assets/img/icon-32.png) .NET DependencyInjection via conventions or [Service] attributes  \n============\n\n[![Version](https://img.shields.io/nuget/vpre/Devlooped.Extensions.DependencyInjection.svg)](https://www.nuget.org/packages/Devlooped.Extensions.DependencyInjection)\n[![Downloads](https://img.shields.io/nuget/dt/Devlooped.Extensions.DependencyInjection.svg)](https://www.nuget.org/packages/Devlooped.Extensions.DependencyInjection)\n[![License](https://img.shields.io/github/license/devlooped/DependencyInjection.svg?color=blue)](https://github.com//devlooped/DependencyInjection/blob/main/license.txt)\n[![Build](https://github.com/devlooped/DependencyInjection/actions/workflows/build.yml/badge.svg)](https://github.com/devlooped/DependencyInjection/actions/workflows/build.yml)\n\n\u003c!-- include https://github.com/devlooped/.github/raw/main/sponsorlinkr.md --\u003e\n*This project uses [SponsorLink](https://github.com/devlooped#sponsorlink) to attribute sponsor status (direct, indirect or implicit).*\n*For IDE usage (without warnings), sponsor status is required. IDE-only warnings will be issued after a grace period otherwise.*\n\n\u003c!-- https://github.com/devlooped/.github/raw/main/sponsorlinkr.md --\u003e\n\n\u003c!-- #content --\u003e\n\nAutomatic compile-time service registrations for Microsoft.Extensions.DependencyInjection with no run-time dependencies, \nfrom conventions or attributes.\n\n## Usage\n\nThe package supports two complementary ways to register services in the DI container, both of which are source-generated at compile-time \nand therefore have no run-time dependencies or reflection overhead:\n\n- **Attribute-based**: annotate your services with `[Service]` or `[Service\u003cTKey\u003e]` attributes to register them in the DI container.\n- **Convention-based**: register services by type or name using a convention-based approach.\n\n### Attribute-based\n\nThe `[Service(ServiceLifetime)]` attribute is available to explicitly annotate types for registration:\n\n```csharp\n[Service(ServiceLifetime.Scoped)]\npublic class MyService : IMyService, IDisposable\n{\n    public string Message =\u003e \"Hello World\";\n\n    public void Dispose() { }\n}\n\npublic interface IMyService \n{\n    string Message { get; }\n}\n```\n\nThe `ServiceLifetime` argument is optional and defaults to [ServiceLifetime.Singleton](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.servicelifetime?#fields).\n\n\u003e NOTE: The attribute is matched by simple name, so you can define your own attribute \n\u003e in your own assembly. It only has to provide a constructor receiving a \n\u003e [ServiceLifetime](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.servicelifetime) argument, \n\u003e and optionally an overload receiving an `object key` for keyed services.\n\nA source generator will emit (at compile-time) an `AddServices` extension method for \n[IServiceCollection](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.iservicecollection) \nwhich you can call from your startup code that sets up your services, like:\n\n```csharp\nvar builder = WebApplication.CreateBuilder(args);\n\n// NOTE: **Adds discovered services to the container**\nbuilder.Services.AddServices();\n// ...\n\nvar app = builder.Build();\n\n// Configure the HTTP request pipeline.\napp.MapGet(\"/\", (IMyService service) =\u003e service.Message);\n\n// ...\napp.Run();\n```\n\n\u003e NOTE: the service is available automatically for the scoped request, because \n\u003e we called the generated `AddServices` that registers the discovered services. \n\nAnd that's it. The source generator will discover annotated types in the current \nproject and all its references too. Since the registration code is generated at \ncompile-time, there is no run-time reflection (or dependencies) whatsoever.\n\n### Convention-based\n\nYou can also avoid attributes entirely by using a convention-based approach, which \nis nevertheless still compile-time checked and source-generated. This allows \nregistering services for which you don't even have the source code to annotate:\n\n```csharp\nvar builder = WebApplication.CreateBuilder(args);\n\nbuilder.Services.AddServices(typeof(IRepository), ServiceLifetime.Scoped);\n// ...\n```\n\nThis will register all types in the current project and its references that are \nassignable to `IRepository`, with the specified lifetime.\n\nYou can also use a regular expression to match services by name instead:\n\n```csharp\nvar builder = WebApplication.CreateBuilder(args);\n\nbuilder.Services.AddServices(\".*Service$\");  // defaults to ServiceLifetime.Singleton\n// ...\n```\n\nYou can use a combination of both, as needed. In all cases, NO run-time reflection is \never performed, and the compile-time source generator will evaluate the types that are \nassignable to the given type or matching full type names and emit the typed registrations \nas needed.\n\n### Keyed Services\n\n[Keyed services](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-8.0#keyed-services) \nare also supported by providing a key with the `[Service]` attribute. For example:\n\n```csharp\npublic interface INotificationService\n{\n    string Notify(string message);\n}\n\n[Service(\"sms\")]\npublic class SmsNotificationService : INotificationService\n{\n    public string Notify(string message) =\u003e $\"[SMS] {message}\";\n}\n\n[Service(\"email\")]\n[Service(\"default\")]\npublic class EmailNotificationService : INotificationService\n{\n    public string Notify(string message) =\u003e $\"[Email] {message}\";\n}\n```\n\nServices that want to consume a specific keyed service can use the \n`[FromKeyedServices(object key)]` attribute to specify the key, like:\n\n```csharp\n[Service]\npublic class SmsService([FromKeyedServices(\"sms\")] INotificationService sms)\n{\n    public void DoSomething() =\u003e sms.Notify(\"Hello\");\n}\n```\n\nIn this case, when resolving the `SmsService` from the service provider, the \nright `INotificationService` will be injected, based on the key provided.\n\nNote you can also register the same service using multiple keys, as shown in the \n`EmailNotificationService` above.\n\n\u003e Keyed services are a feature of version 8.0+ of Microsoft.Extensions.DependencyInjection\n\n## How It Works\n\nIn all cases, the generated code that implements the registration looks like the following:\n\n```csharp\nstatic partial class AddServicesExtension\n{\n    public static IServiceCollection AddServices(this IServiceCollection services)\n    {\n        services.TryAddScoped(s =\u003e new MyService());\n        services.AddScoped\u003cIMyService\u003e(s =\u003e s.GetRequiredService\u003cMyService\u003e());\n        services.AddScoped\u003cIDisposable\u003e(s =\u003e s.GetRequiredService\u003cMyService\u003e());\n        \n        return services;\n    }\n```\n\nNote how the service is registered as scoped with its own type first, and the \nother two registrations just retrieve the same service (according to its defined \nlifetime). This means the instance is reused and properly registered under \nall implemented interfaces automatically.\n\n\u003e NOTE: you can inspect the generated code by setting `EmitCompilerGeneratedFiles=true` \n\u003e in your project file and browsing the `generated` subfolder under `obj`.\n\nIf the service type has dependencies, they will be resolved from the service \nprovider by the implementation factory too, like:\n\n```csharp\nservices.TryAddScoped(s =\u003e new MyService(s.GetRequiredService\u003cIMyDependency\u003e(), ...));\n```\n\nKeyed services will emit TryAddKeyedXXX methods instead.\n\n## MEF Compatibility\n\nGiven the (more or less broad?) adoption of \n[MEF attribute](https://learn.microsoft.com/en-us/dotnet/framework/mef/attributed-programming-model-overview-mef)\n(whether [.NET MEF, NuGet MEF or VS MEF](https://github.com/microsoft/vs-mef/blob/main/doc/mef_library_differences.md)) in .NET, \nthe generator also supports the `[Export]` attribute to denote a service (the \ntype argument as well as contract name are ignored, since those aren't supported \nin the DI container). \n\nIn order to specify a singleton (shared) instance in MEF, you have to annotate the \ntype with an extra attribute: `[Shared]` in NuGet MEF (from [System.Composition](http://nuget.org/packages/System.Composition.AttributedModel)) \nor `[PartCreationPolicy(CreationPolicy.Shared)]` in .NET MEF \n(from [System.ComponentModel.Composition](https://www.nuget.org/packages/System.ComponentModel.Composition)).\n\nBoth `[Export(\"contractName\")]` and `[Import(\"contractName\")]` are supported and \nwill be used to register and resolve keyed services respectively, meaning you can \ntypically depend on just `[Export]` and `[Import]` attributes for all your DI \nannotations and have them work automatically when composed in the DI container.\n\n## Advanced Scenarios\n\n### `Lazy\u003cT\u003e` and `Func\u003cT\u003e` Dependencies\n\nA `Lazy\u003cT\u003e` for each interface (and main implementation) is automatically provided \ntoo, so you can take a lazy dependency out of the box too. In this case, the lifetime \nof the dependency `T` becomes tied to the lifetime of the component taking the lazy \ndependency, for obvious reasons. The `Lazy\u003cT\u003e` is merely a lazy resolving of the \ndependency via the service provider. The lazy itself isn't costly to construct, and \nsince the lifetime of the underlying service, plus the lifetime of the consuming \nservice determine the ultimate lifetime of the lazy, no additional configuration is \nnecessary for it, as it's always registered as a transient component. Generated code \nlooks like the following:\n\n```csharp\nservices.AddTransient(s =\u003e new Lazy\u003cIMyService\u003e(s.GetRequiredService\u003cMyService\u003e));\n```\n\nA `Func\u003cT\u003e` is also automatically registered, but it is just a delegate to the \nactual `IServiceProvider.GetRequiredService\u003cT\u003e`. Generated code looks like the \nfollowing:\n\n\n```csharp\nservices.AddTransient\u003cFunc\u003cIMyService\u003e\u003e(s =\u003e s.GetRequiredService\u003cMyService\u003e);\n```\n\nRepeatedly invoking the function will result in an instance of the required \nservice that depends on the registered lifetime for it. If it was registered \nas a singleton, for example, you would get the same value every time, just \nas if you had used a dependency of `Lazy\u003cT\u003e` instead, but invoking the \nservice provider each time, instead of only once. This makes this pattern \nmore useful for transient services that you intend to use for a short time \n(and potentially dispose afterwards).\n\n\n### Your Own ServiceAttribute\n\nIf you want to declare your own `ServiceAttribute` and reuse from your projects, \nso as to avoid taking a (development-only, compile-time only) dependency on this \npackage from your library projects, you can just declare it like so:\n\n```csharp\n[AttributeUsage(AttributeTargets.Class)]\npublic class ServiceAttribute : Attribute\n{\n    public ServiceAttribute(ServiceLifetime lifetime = ServiceLifetime.Singleton) { }\n    public ServiceAttribute(object key, ServiceLifetime lifetime = ServiceLifetime.Singleton) { }\n}\n```\n\n\u003e NOTE: since the constructor arguments are only used by the source generation to \n\u003e detemine the registration style (and key), but never at run-time, you don't even need \n\u003e to keep it around in a field or property!\n\nWith this in place, you only need to add this package to the top-level project \nthat is adding the services to the collection!\n\nThe attribute is matched by simple name, so it can exist in any namespace. \n\nIf you want to avoid adding the attribute to the project referencing this package, \nset the `$(AddServiceAttribute)` to `false` via MSBuild:\n\n```xml\n\u003cPropertyGroup\u003e\n  \u003cAddServiceAttribute\u003efalse\u003c/AddServiceAttribute\u003e\n\u003c/PropertyGroup\u003e\n```\n\nIf you want to avoid generating the `AddServices` extension method to the project referencing \nthis package, set the `$(AddServicesExtension)` to `false` via MSBuild:\n\n```xml\n\u003cPropertyGroup\u003e\n  \u003cAddServicesExtension\u003efalse\u003c/AddServicesExtension\u003e\n\u003c/PropertyGroup\u003e\n```\n\n### Choose Constructor\n\nIf you want to choose a specific constructor to be used for the service implementation \nfactory registration (instead of the default one which will be the one with the most \nparameters), you can annotate it with `[ImportingConstructor]` from either NuGet MEF \n([System.Composition](http://nuget.org/packages/System.Composition.AttributedModel)) \nor .NET MEF ([System.ComponentModel.Composition](https://www.nuget.org/packages/System.ComponentModel.Composition)).\n\n\u003c!-- #content --\u003e\n\n# Dogfooding\n\n[![CI Version](https://img.shields.io/endpoint?url=https://shields.kzu.app/vpre/Devlooped.Extensions.DependencyInjection/main\u0026label=nuget.ci\u0026color=brightgreen)](https://pkg.kzu.app/index.json)\n[![Build](https://github.com/devlooped/DependencyInjection/actions/workflows/build.yml/badge.svg)](https://github.com/devlooped/DependencyInjection/actions/workflows/build.yml)\n\nWe also produce CI packages from branches and pull requests so you can dogfood builds as quickly as they are produced. \n\nThe CI feed is `https://pkg.kzu.app/index.json`. \n\nThe versioning scheme for packages is:\n\n- PR builds: *42.42.42-pr*`[NUMBER]`\n- Branch builds: *42.42.42-*`[BRANCH]`.`[COMMITS]`\n\n\n\u003c!-- include https://github.com/devlooped/sponsors/raw/main/footer.md --\u003e\n# Sponsors \n\n\u003c!-- sponsors.md --\u003e\n[![Clarius Org](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/clarius.png \"Clarius Org\")](https://github.com/clarius)\n[![MFB Technologies, Inc.](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/MFB-Technologies-Inc.png \"MFB Technologies, Inc.\")](https://github.com/MFB-Technologies-Inc)\n[![Torutek](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/torutek-gh.png \"Torutek\")](https://github.com/torutek-gh)\n[![DRIVE.NET, Inc.](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/drivenet.png \"DRIVE.NET, Inc.\")](https://github.com/drivenet)\n[![Keith Pickford](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/Keflon.png \"Keith Pickford\")](https://github.com/Keflon)\n[![Thomas Bolon](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/tbolon.png \"Thomas Bolon\")](https://github.com/tbolon)\n[![Kori Francis](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/kfrancis.png \"Kori Francis\")](https://github.com/kfrancis)\n[![Toni Wenzel](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/twenzel.png \"Toni Wenzel\")](https://github.com/twenzel)\n[![Uno Platform](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/unoplatform.png \"Uno Platform\")](https://github.com/unoplatform)\n[![Dan Siegel](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/dansiegel.png \"Dan Siegel\")](https://github.com/dansiegel)\n[![Reuben Swartz](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/rbnswartz.png \"Reuben Swartz\")](https://github.com/rbnswartz)\n[![Jacob Foshee](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/jfoshee.png \"Jacob Foshee\")](https://github.com/jfoshee)\n[![](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/Mrxx99.png \"\")](https://github.com/Mrxx99)\n[![Eric Johnson](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/eajhnsn1.png \"Eric Johnson\")](https://github.com/eajhnsn1)\n[![David JENNI](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/davidjenni.png \"David JENNI\")](https://github.com/davidjenni)\n[![Jonathan ](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/Jonathan-Hickey.png \"Jonathan \")](https://github.com/Jonathan-Hickey)\n[![Charley Wu](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/akunzai.png \"Charley Wu\")](https://github.com/akunzai)\n[![Ken Bonny](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/KenBonny.png \"Ken Bonny\")](https://github.com/KenBonny)\n[![Simon Cropp](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/SimonCropp.png \"Simon Cropp\")](https://github.com/SimonCropp)\n[![agileworks-eu](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/agileworks-eu.png \"agileworks-eu\")](https://github.com/agileworks-eu)\n[![sorahex](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/sorahex.png \"sorahex\")](https://github.com/sorahex)\n[![Zheyu Shen](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/arsdragonfly.png \"Zheyu Shen\")](https://github.com/arsdragonfly)\n[![Vezel](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/vezel-dev.png \"Vezel\")](https://github.com/vezel-dev)\n[![ChilliCream](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/ChilliCream.png \"ChilliCream\")](https://github.com/ChilliCream)\n[![4OTC](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/4OTC.png \"4OTC\")](https://github.com/4OTC)\n[![Vincent Limo](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/v-limo.png \"Vincent Limo\")](https://github.com/v-limo)\n[![Jordan S. Jones](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/jordansjones.png \"Jordan S. Jones\")](https://github.com/jordansjones)\n[![domischell](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/DominicSchell.png \"domischell\")](https://github.com/DominicSchell)\n[![Mauricio Scheffer](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/mausch.png \"Mauricio Scheffer\")](https://github.com/mausch)\n[![Justin Wendlandt](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/jwendl.png \"Justin Wendlandt\")](https://github.com/jwendl)\n[![Adrian Alonso](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/adalon.png \"Adrian Alonso\")](https://github.com/adalon)\n[![Michael Hagedorn](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/Eule02.png \"Michael Hagedorn\")](https://github.com/Eule02)\n[![Matt Frear](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/mattfrear.png \"Matt Frear\")](https://github.com/mattfrear)\n\n\n\u003c!-- sponsors.md --\u003e\n\n[![Sponsor this project](https://raw.githubusercontent.com/devlooped/sponsors/main/sponsor.png \"Sponsor this project\")](https://github.com/sponsors/devlooped)\n\u0026nbsp;\n\n[Learn more about GitHub Sponsors](https://github.com/sponsors)\n\n\u003c!-- https://github.com/devlooped/sponsors/raw/main/footer.md --\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevlooped%2Fdependencyinjection","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdevlooped%2Fdependencyinjection","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevlooped%2Fdependencyinjection/lists"}