The Issue:
Trying to implement an auto dependency injection registrator, my conventions are very strict so it will be very useful.
I'm having issues with registering classes containing async methods, the container seems to adress to these methods while registering the class.
About the project:
- The Soruce generator is netstandard2.0
- The Models and the Executable are net5.0
- When the method is not asynchronous everything works perfectly
- The MetadataReference are not actually needed I just want to avoid answers addressing to those
TL;DL
Some of the reproduction errors:
- CS0103 The name 'd__0' does not exist in the current context
- CS0103 The name 'TestMethod' does not exist in the current context
- CS1525 Invalid expression term '<'
- CS1002 ; expected
The code:
Source Generator Project:
namespace Test.Build.Tools
{
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
/// <summary>
/// Auto register source generator.
/// </summary>
[Generator]
public class AutoRegisterSourceGenerator : ISourceGenerator
{
/// <inheritdoc/>
public void Initialize(GeneratorInitializationContext context)
{
}
/// <inheritdoc/>
public void Execute(GeneratorExecutionContext context)
{
StringBuilder stringBuilder = new("namespace Test.Extensions.DependencyInjection\n"
+ "{\n"
+ " using System;\n"
+ " using System.Threading.Tasks;\n"
+ " using Microsoft.Extensions.DependencyInjection;\n");
List<string> namespaces = new();
string defaultPath = typeof(object).Assembly.Location.Replace("mscorlib", "{0}");
List<MetadataReference> references = new()
{
{ MetadataReference.CreateFromFile(string.Format(defaultPath, "System.Threading.Tasks")) }
};
var types = GetAllTypes(context.Compilation);
var neededTypes = types.Where(t =>
{
string @namespace = t.ContainingNamespace.ToString();
if (@namespace.Contains("Test")
&& !t.Interfaces.IsEmpty
&& t.TypeKind == TypeKind.Class)
{
namespaces.Add(t.ContainingNamespace.ToString());
namespaces.Add(t.Interfaces[0].ContainingNamespace.ToString());
return true;
}
return false;
}).ToList();
namespaces.Distinct().OrderBy(n => n.ToString()).ToList().ForEach(n => stringBuilder.Append($" using {n};\n"));
stringBuilder.Append(
" /// <summary>\n" +
" /// Service registrator class.\n" +
" /// </summary>\n" +
" public static class ServicesRegistrator\n" +
" {\n" +
" /// <summary>\n" +
" /// Register dependency injection instances.\n" +
" /// </summary>\n" +
" /// <param name=\"services\">Startup services.</param>\n" +
" /// <returns>The given <see cref=\"IServiceCollection\"/> instance.</returns>\n" +
" public static IServiceCollection RegisterDomainModel(this IServiceCollection services)\n" +
" {\n");
foreach (var type in neededTypes)
{
stringBuilder.Append($" services.AddScoped<I{type.Name}, {type.Name}>();");
stringBuilder.AppendLine();
}
stringBuilder.Append(" return services;\n" +
" }\n" +
" }\n" +
"}\n");
context.Compilation.AddReferences(references);
context.AddSource("ServicesRegistrator", SourceText.From(stringBuilder.ToString(), Encoding.UTF8));
}
IEnumerable<INamedTypeSymbol> GetAllTypes(Compilation compilation) =>
GetAllTypes(compilation.GlobalNamespace);
IEnumerable<INamedTypeSymbol> GetAllTypes(INamespaceSymbol @namespace)
{
foreach (var type in @namespace.GetTypeMembers())
foreach (var nestedType in GetNestedTypes(type))
yield return nestedType;
foreach (var nestedNamespace in @namespace.GetNamespaceMembers())
foreach (var type in GetAllTypes(nestedNamespace))
yield return type;
}
IEnumerable<INamedTypeSymbol> GetNestedTypes(INamedTypeSymbol type)
{
yield return type;
foreach (var nestedType in type.GetTypeMembers()
.SelectMany(nestedType => GetNestedTypes(nestedType)))
yield return nestedType;
}
}
}
Models Project:
namespace TestClasses
{
using System.Threading.Tasks;
public interface ITestClass
{
public Task TestMethod();
}
}
namespace TestClasses.Model
{
using System.Threading.Tasks;
public class TestClass : ITestClass
{
public async Task TestMethod()
{
await Task.CompletedTask;
}
}
}
Executable
using Executable;
Program.Rgister();
namespace Executable
{
using Microsoft.Extensions.DependencyInjection;
using Test.Extensions.DependencyInjection;
using TestClasses;
public class Program
{
public static void Rgister()
{
IServiceCollection services = new ServiceCollection();
services.RegisterDomainModel();
var x = services.BuildServiceProvider().GetRequiredService<ITestClass>();
x.TestMethod();
}
}
}
Update:
The Generated Code:
namespace Test.Extensions.DependencyInjection
{
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using TestClasses;
using TestClasses.Model;
/// <summary>
/// Service registrator class.
/// </summary>
public static class ServicesRegistrator
{
/// <summary>
/// Register dependency injection instances.
/// </summary>
/// <param name="services">Startup services.</param>
/// <returns>The given <see cref="IServiceCollection"/> instance.</returns>
public static IServiceCollection RegisterDomainModel(this IServiceCollection services)
{
services.AddScoped<ITestClass, TestClass>();
return services;
}
}
}