Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@

<!-- #content -->

Automatic compile-time service registrations for Microsoft.Extensions.DependencyInjection with no run-time dependencies.
Automatic compile-time service registrations for Microsoft.Extensions.DependencyInjection with no run-time dependencies,
from conventions or attributes.

## Usage

Expand Down Expand Up @@ -269,6 +270,21 @@ parameters), you can annotate it with `[ImportingConstructor]` from either NuGet
([System.Composition](http://nuget.org/packages/System.Composition.AttributedModel))
or .NET MEF ([System.ComponentModel.Composition](https://www.nuget.org/packages/System.ComponentModel.Composition)).

### Customize Generated Class

You can customize the generated class namespace and name with the following
MSBuild properties:

```xml
<PropertyGroup>
<AddServicesNamespace>MyNamespace</AddServicesNamespace>
<AddServicesClassName>MyExtensions</AddServicesClassName>
</PropertyGroup>
```

They default to `Microsoft.Extensions.DependencyInjection` and `AddServicesNoReflectionExtension`
respectively.

<!-- #content -->

# Dogfooding
Expand Down
24 changes: 12 additions & 12 deletions src/CodeAnalysis.Tests/AddServicesAnalyzerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ public static void Main()
{
Sources =
{
ThisAssembly.Resources.AddServicesNoReflectionExtension.Text,
ThisAssembly.Resources.ServiceAttribute.Text,
ThisAssembly.Resources.ServiceAttribute_1.Text,
StaticGenerator.AddServicesExtension,
StaticGenerator.ServiceAttribute,
StaticGenerator.ServiceAttributeT,
},
ReferenceAssemblies = new ReferenceAssemblies(
"net8.0",
Expand Down Expand Up @@ -89,9 +89,9 @@ public static void Main()
{
Sources =
{
ThisAssembly.Resources.AddServicesNoReflectionExtension.Text,
ThisAssembly.Resources.ServiceAttribute.Text,
ThisAssembly.Resources.ServiceAttribute_1.Text,
StaticGenerator.AddServicesExtension,
StaticGenerator.ServiceAttribute,
StaticGenerator.ServiceAttributeT,
},
ReferenceAssemblies = new ReferenceAssemblies(
"net8.0",
Expand Down Expand Up @@ -130,9 +130,9 @@ public static void Main()
{
Sources =
{
ThisAssembly.Resources.AddServicesNoReflectionExtension.Text,
ThisAssembly.Resources.ServiceAttribute.Text,
ThisAssembly.Resources.ServiceAttribute_1.Text,
StaticGenerator.AddServicesExtension,
StaticGenerator.ServiceAttribute,
StaticGenerator.ServiceAttributeT,
},
ReferenceAssemblies = new ReferenceAssemblies(
"net8.0",
Expand Down Expand Up @@ -177,9 +177,9 @@ public static void Main()
{
Sources =
{
ThisAssembly.Resources.AddServicesNoReflectionExtension.Text,
ThisAssembly.Resources.ServiceAttribute.Text,
ThisAssembly.Resources.ServiceAttribute_1.Text,
StaticGenerator.AddServicesExtension,
StaticGenerator.ServiceAttribute,
StaticGenerator.ServiceAttributeT,
},
ReferenceAssemblies = new ReferenceAssemblies(
"net8.0",
Expand Down
2 changes: 0 additions & 2 deletions src/CodeAnalysis.Tests/CodeAnalysis.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,4 @@
<ProjectReference Include="..\DependencyInjection\DependencyInjection.csproj" />
</ItemGroup>

<Import Project="..\DependencyInjection.Tests\ContentFiles.targets" />

</Project>
18 changes: 9 additions & 9 deletions src/CodeAnalysis.Tests/ConventionAnalyzerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ public static void Main()
{
Sources =
{
ThisAssembly.Resources.AddServicesNoReflectionExtension.Text,
ThisAssembly.Resources.ServiceAttribute.Text,
ThisAssembly.Resources.ServiceAttribute_1.Text,
StaticGenerator.AddServicesExtension,
StaticGenerator.ServiceAttribute,
StaticGenerator.ServiceAttributeT,
},
ReferenceAssemblies = new ReferenceAssemblies(
"net8.0",
Expand Down Expand Up @@ -86,9 +86,9 @@ public static void Main()
{
Sources =
{
ThisAssembly.Resources.AddServicesNoReflectionExtension.Text,
ThisAssembly.Resources.ServiceAttribute.Text,
ThisAssembly.Resources.ServiceAttribute_1.Text,
StaticGenerator.AddServicesExtension,
StaticGenerator.ServiceAttribute,
StaticGenerator.ServiceAttributeT,
},
ReferenceAssemblies = new ReferenceAssemblies(
"net8.0",
Expand Down Expand Up @@ -133,9 +133,9 @@ public static void Main()
{
Sources =
{
ThisAssembly.Resources.AddServicesNoReflectionExtension.Text,
ThisAssembly.Resources.ServiceAttribute.Text,
ThisAssembly.Resources.ServiceAttribute_1.Text,
StaticGenerator.AddServicesExtension,
StaticGenerator.ServiceAttribute,
StaticGenerator.ServiceAttributeT,
},
ReferenceAssemblies = new ReferenceAssemblies(
"net8.0",
Expand Down
16 changes: 0 additions & 16 deletions src/DependencyInjection.Tests/ContentFiles.targets

This file was deleted.

44 changes: 23 additions & 21 deletions src/DependencyInjection.Tests/ConventionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,39 +11,41 @@ namespace Tests.DependencyInjection;

public class ConventionsTests(ITestOutputHelper Output)
{
[Fact]
public void RegisterRepositoryServices()
{
var conventions = new ServiceCollection();
conventions.AddSingleton(Output);
conventions.AddServices(typeof(IRepository));
var services = conventions.BuildServiceProvider();
//[Fact]
//public void RegisterRepositoryServices()
//{
// var conventions = new ServiceCollection();
// conventions.AddSingleton(Output);
// conventions.AddServices(typeof(IRepository));
// var services = conventions.BuildServiceProvider();

var instance = services.GetServices<IRepository>().ToList();
// var instance = services.GetServices<IRepository>().ToList();

Assert.Equal(2, instance.Count);
}
// Assert.Equal(2, instance.Count);
//}

[Fact]
public void RegisterServiceByRegex()
{
var conventions = new ServiceCollection();
conventions.AddSingleton(Output);
conventions.AddServices(nameof(ConventionsTests), ServiceLifetime.Transient);
var services = conventions.BuildServiceProvider();
//[Fact]
//public void RegisterServiceByRegex()
//{
// var conventions = new ServiceCollection();
// conventions.AddSingleton(Output);
// conventions.AddServices(nameof(ConventionsTests), ServiceLifetime.Transient);
// var services = conventions.BuildServiceProvider();

var instance = services.GetRequiredService<ConventionsTests>();
var instance2 = services.GetRequiredService<ConventionsTests>();
// var instance = services.GetRequiredService<ConventionsTests>();
// var instance2 = services.GetRequiredService<ConventionsTests>();

Assert.NotSame(instance, instance2);
}
// Assert.NotSame(instance, instance2);
//}

[Fact]
public void RegisterGenericServices()
{
var conventions = new ServiceCollection();

#pragma warning disable DDI003
conventions.AddServices(typeof(IGenericRepository<>), ServiceLifetime.Scoped);
#pragma warning restore DDI003

var services = conventions.BuildServiceProvider();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
<Using Include="Xunit.Abstractions" />
</ItemGroup>

<Import Project="ContentFiles.targets" />
<Import Project="..\DependencyInjection\Devlooped.Extensions.DependencyInjection.targets" />

</Project>
15 changes: 12 additions & 3 deletions src/DependencyInjection/AddServicesAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,16 @@ public override void Initialize(AnalysisContext context)

Location? location = default;

static bool IsDDICode(SyntaxNode node, SemanticModel semantic)
{
if (node.Ancestors().OfType<MethodDeclarationSyntax>().FirstOrDefault() is { } method &&
semantic.GetDeclaredSymbol(method) is { } declaration &&
declaration.GetAttributes().Any(attr => attr.AttributeClass?.Name == "DDIAddServicesAttribute"))
return true;

return false;
}

startContext.RegisterSemanticModelAction(semanticContext =>
{
var semantic = semanticContext.SemanticModel;
Expand All @@ -48,9 +58,8 @@ public override void Initialize(AnalysisContext context)
.DescendantNodes()
.OfType<InvocationExpressionSyntax>()
.Select(invocation => new { Invocation = invocation, semantic.GetSymbolInfo(invocation, semanticContext.CancellationToken).Symbol })
// We don't consider invocations from methods that have the DDIAddServicesAttribute as user-provided, since we do that
// in our type/regex overloads. Users need to invoke those methods in turn.
.Where(x => x.Symbol is IMethodSymbol method && !method.GetAttributes().Any(attr => attr.AttributeClass?.Name == "DDIAddServicesAttribute"))
// It has to be user-provided code, not our own extensions/overloads.
.Where(x => !IsDDICode(x.Invocation, semantic))
.Select(x => new { x.Invocation, Method = (IMethodSymbol)x.Symbol! });

bool IsServiceCollectionExtension(IMethodSymbol method) => method.IsExtensionMethod &&
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.ComponentModel;
using Microsoft.Extensions.DependencyInjection;

namespace Microsoft.Extensions.DependencyInjection
{
Expand Down
26 changes: 17 additions & 9 deletions src/DependencyInjection/DependencyInjection.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
<TargetFramework>netstandard2.0</TargetFramework>
<AssemblyName>Devlooped.Extensions.DependencyInjection</AssemblyName>
<PackageId>Devlooped.Extensions.DependencyInjection</PackageId>
<Title>Automatic compile-time service registrations for Microsoft.Extensions.DependencyInjection with no run-time dependencies.</Title>
<Title>
Automatic compile-time service registrations for Microsoft.Extensions.DependencyInjection with no run-time dependencies, from conventions or attributes.
</Title>
<Description>$(Title)</Description>
<PackFolder>analyzers/dotnet</PackFolder>
<IsRoslynComponent>true</IsRoslynComponent>
Expand All @@ -14,26 +16,32 @@
<ImplicitUsings>false</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<Compile Remove="StaticGenerator.cs" />
</ItemGroup>

<ItemGroup>
<None Include="StaticGenerator.cs" />
</ItemGroup>

<ItemGroup>
<None Update="Devlooped.Extensions.DependencyInjection.props" CopyToOutputDirectory="PreserveNewest" PackFolder="buildTransitive" />
<None Update="Devlooped.Extensions.DependencyInjection.targets" CopyToOutputDirectory="PreserveNewest" PackFolder="buildTransitive" />
<!--
<Compile Update="AddServicesNoReflectionExtension.cs" Pack="true" />
<Compile Update="ServiceAttribute*.cs" Pack="true" />
-->
<EmbeddedCode Include="ServiceAttribute*.cs;AddServicesNoReflectionExtension.cs" />
</ItemGroup>

<Target Name="CopyEmbeddedCode" Inputs="@(EmbeddedCode)" Outputs="@(EmbeddedCode -> '$(IntermediateOutputPath)%(Filename).txt')">
<Copy SourceFiles="@(EmbeddedCode)" DestinationFiles="@(EmbeddedCode -> '$(IntermediateOutputPath)%(Filename).txt')" SkipUnchangedFiles="true" />
</Target>

<Target Name="AddEmbeddedResources" DependsOnTargets="CopyEmbeddedCode" BeforeTargets="SplitResourcesByCulture">
<ItemGroup>
<EmbeddedResource Include="@(EmbeddedCode -> '$(IntermediateOutputPath)%(Filename).txt')" Link="%(EmbeddedCode.Filename).txt" Type="Non-Resx" />
</ItemGroup>
</Target>

<ItemGroup>
<PackageReference Include="NuGetizer" Version="1.2.1" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.2.0" Pack="false" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
<PackageReference Include="PolySharp" Version="1.14.1" PrivateAssets="all" />
<PackageReference Include="ThisAssembly.Resources" Version="2.0.8" PrivateAssets="all" />
</ItemGroup>

<Target Name="PokePackageVersion" BeforeTargets="GetPackageContents" DependsOnTargets="CopyFilesToOutputDirectory" Condition="'$(dotnet-nugetize)' == '' and Exists('$(OutputPath)\Devlooped.Extensions.DependencyInjection.props')">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

<ItemGroup>
<CompilerVisibleProperty Include="IsTestProject" />
<CompilerVisibleProperty Include="AddServicesNamespace" />
<CompilerVisibleProperty Include="AddServicesClassName" />
</ItemGroup>

</Project>
Loading