Skip to content

Commit

Permalink
Merge pull request #9 from futurum-dev/feature/improvements-to-Module…
Browse files Browse the repository at this point in the history
…-and-Startable

Improvements to Modules and Startables
  • Loading branch information
futurum-dev authored Apr 17, 2023
2 parents dd99ea2 + 1b41e88 commit d35e7be
Show file tree
Hide file tree
Showing 15 changed files with 217 additions and 84 deletions.
130 changes: 71 additions & 59 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@
[![Coverage Status](https://img.shields.io/coveralls/github/futurum-dev/dotnet.futurum.microsoft.extensions.dependencyinjection?style=for-the-badge)](https://coveralls.io/github/futurum-dev/dotnet.futurum.microsoft.extensions.dependencyinjection?branch=main)
[![NuGet version](https://img.shields.io/nuget/v/futurum.microsoft.extensions.dependencyinjection?style=for-the-badge)](https://www.nuget.org/packages/futurum.microsoft.extensions.dependencyinjection)

A dotnet library, that allows Microsoft.Extensions.DependencyInjection to work with Futurum.Core. It also adds support for modules and startables.
A dotnet library that extends Microsoft.Extensions.DependencyInjection by adding support for [modules](#modules), [startables](#startables) and [attribute based registration](#attribute-based-registration).

- [x] Autodiscovery of DependencyInjection registrations, based on [attributes](#attribute-based-registration) and Source Generators
- [x] Autodiscovery of DependencyInjection modules, based on [attributes](#attribute-based-module) and Source Generators
- [x] Autodiscovery of DependencyInjection startables, based on [attributes](#attribute-based-startable) and Source Generators
- [x] [Roslyn Analysers](#roslyn-analysers) to help build your WebApiEndpoint(s), using best practices
- [x] Integration with Futurum.Core]

## TryGetService
Try to get the service object of the specified type.
Expand All @@ -22,7 +23,10 @@ var result = serviceProvider.TryGetService<ITestService>();
## Modules
A module allows you to break up registration into logical units.

### IModule interface
Module can either be registered using *IModule* interface and *AddModule* extension method, or by using the [*RegisterAsDependencyInjectionModule*](#attribute-based-module) attribute.

### IModule interface and AddModule extension method
#### IModule interface
Implements this interface to create a module.

```csharp
Expand All @@ -35,7 +39,7 @@ public class TestModule : IModule
}
```

### AddModule extension method
#### AddModule extension method
Allows you to register a module.

```csharp
Expand All @@ -46,23 +50,51 @@ services.AddModule<TestModule>();
services.AddModule(new TestModule());
```

### Attribute based module
You can also register modules using attributes.

- RegisterAsDependencyInjectionModule attribute

```csharp
public class Module
{
[RegisterAsDependencyInjectionModule]
public void Load(IServiceCollection services)
{
}
}
```

```csharp
public static class Module
{
[RegisterAsDependencyInjectionModule]
public static void Load(IServiceCollection services)
{
}
}
```

## Startables
A startable is resolved at the start of the application lifecycle and is a place to perform actions as soon as the DependencyInjection container is built.

### IStartable interface
Startable can either be registered using *IStartable* interface and *AddStartable* extension method, or by using the [*RegisterAsDependencyInjectionStartable*](#attribute-based-startable) attribute.

### IStartable interface and AddStartable extension method
#### IStartable interface
Implements this interface to create a startable.

```csharp
public class TestStartable : IStartable
{
public void Start()
public Task Start()
{
// Do something
}
}
```

### AddStartable extension method
#### AddStartable extension method
Allows you to register a startable.

```csharp
Expand All @@ -73,11 +105,39 @@ services.AddStartable<TestStartable>();
services.AddStartable(new TestStartable());
```

### Attribute based startable
You can also register modules using attributes.

- RegisterAsDependencyInjectionStartable attribute

```csharp
public class Startable
{
[RegisterAsDependencyInjectionStartable]
public Task Start()
{
// Do something
}
}
```

```csharp
public static class Startable
{
[RegisterAsDependencyInjectionStartable]
public static Task Start()
{
// Do something
}
}
```

### BuildServiceProviderWithStartables extension method
Creates a ServiceProvider containing services from the provided IServiceCollection and starts all *IStartable* instances.
If you are manually building the *IServiceProvider*, then you need to use *BuildServiceProviderWithStartablesAsync* extension method.
This will build the container as usual, but also starts all *IStartable* instances.

```csharp
var serviceProvider = services.BuildServiceProviderWithStartables();
var serviceProvider = await services.BuildServiceProviderWithStartablesAsync();
```

## Attribute based registration
Expand Down Expand Up @@ -135,58 +195,10 @@ public class Service : IService
}
```

## Attribute based module
You can also register modules using attributes.

- RegisterAsDependencyInjectionModule attribute

```csharp
public class Module
{
[RegisterAsDependencyInjectionModule]
public void Load(IServiceCollection services)
{
}
}
```

```csharp
public static class Module
{
[RegisterAsDependencyInjectionModule]
public static void Load(IServiceCollection services)
{
}
}
```

## Attribute based startable
You can also register modules using attributes.

- RegisterAsDependencyInjectionStartable attribute

```csharp
public class Startable
{
[RegisterAsDependencyInjectionStartable]
public void Start()
{
}
}
```

```csharp
public static class Startable
{
[RegisterAsDependencyInjectionStartable]
public static void Start()
{
}
}
```

## Roslyn Analysers
- FMEDI0001 - Invalid Module Parameter
- FMEDI0002 - Missing Module Parameter
- FMEDI0003 - Non empty constructor found on Module
- FMEDI0004 - Non empty constructor found on Startable
- FMEDI0004 - Non empty constructor found on Startable
- FMEDI0005 - Non async method found on Startable
- FMEDI0006 - Non void method found on Module
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@ namespace Futurum.Microsoft.Extensions.DependencyInjection.Generator.Sample;
public class InstanceStartable
{
[RegisterAsDependencyInjectionStartable]
public void Register()
public Task Start()
{
return Task.CompletedTask;
}
}

public static class StaticStartable
{
[RegisterAsDependencyInjectionStartable]
public static void Register()
public static Task Start()
{
return Task.CompletedTask;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ namespace Futurum.Microsoft.Extensions.DependencyInjection.Sample;
public class AutomaticInstanceStartable
{
[RegisterAsDependencyInjectionStartable]
public void Start()
public Task Start()
{
return Task.CompletedTask;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ namespace Futurum.Microsoft.Extensions.DependencyInjection.Sample;
public static class AutomaticStaticStartable
{
[RegisterAsDependencyInjectionStartable]
public static void Start()
public static Task<int> Start()
{
return Task.FromResult(1);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public ManualStartable(ILogger logger)
_logger = logger;
}

public void Start()
public async Task StartAsync()
{
_logger.Information($"{nameof(ManualStartable)} started");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,20 @@ public static class DiagnosticDescriptors
"Futurum.Microsoft.Extensions.DependencyInjection.Generator",
DiagnosticSeverity.Error,
true);

public static readonly DiagnosticDescriptor StartableNonAsyncMethod = new(
"FMEDI0005",
"Non async method found on Startable",
$"Startable method '{{0}}' is not async.{Environment.NewLine} Startable methods must be async.",
"Futurum.Microsoft.Extensions.DependencyInjection.Generator",
DiagnosticSeverity.Error,
true);

public static readonly DiagnosticDescriptor ModuleNonVoidReturn = new(
"FMEDI0006",
"Non void method found on Module",
$"Module method '{{0}}' does not return void.{Environment.NewLine} Module methods must return void.",
"Futurum.Microsoft.Extensions.DependencyInjection.Generator",
DiagnosticSeverity.Error,
true);
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,19 @@ public static IEnumerable<Diagnostic> Check(IMethodSymbol methodSymbol, INamedTy
}
}
}

public static class NonVoidReturn
{
public static IEnumerable<Diagnostic> Check(IMethodSymbol methodSymbol)
{
if (methodSymbol.ReturnsVoid)
yield break;

yield return Diagnostic.Create(DiagnosticDescriptors.ModuleNonVoidReturn,
methodSymbol.Locations.First(),
methodSymbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat));
}
}
}

public static class Startable
Expand Down Expand Up @@ -105,5 +118,21 @@ public static IEnumerable<Diagnostic> Check(IMethodSymbol methodSymbol, INamedTy
}
}
}

public static class StartableNonAsyncMethod
{
public static IEnumerable<Diagnostic> Check(IMethodSymbol methodSymbol)
{
if (methodSymbol.IsAsync)
yield break;

if (methodSymbol.ReturnType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat).StartsWith("global::System.Threading.Tasks.Task"))
yield break;

yield return Diagnostic.Create(DiagnosticDescriptors.StartableNonAsyncMethod,
methodSymbol.Locations.First(),
methodSymbol.Name);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System.Collections.Immutable;

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;

namespace Futurum.Microsoft.Extensions.DependencyInjection.Generator;

[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class ModuleNonVoidReturnAnalyzer : DiagnosticAnalyzer
{
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; }
= ImmutableArray.Create(DiagnosticDescriptors.ModuleNonVoidReturn);

public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();

context.RegisterSymbolAction(Execute, SymbolKind.Method);
}

private static void Execute(SymbolAnalysisContext context)
{
if (context.Symbol is not IMethodSymbol methodSymbol)
return;

if (!Diagnostics.Module.HasAttribute(methodSymbol))
return;

var diagnostics = Diagnostics.Module.NonVoidReturn.Check(methodSymbol);

foreach (var diagnostic in diagnostics)
{
context.ReportDiagnostic(diagnostic);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System.Collections.Immutable;

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;

namespace Futurum.Microsoft.Extensions.DependencyInjection.Generator;

[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class StartableNonAsyncMethodAnalyzer : DiagnosticAnalyzer
{
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; }
= ImmutableArray.Create(DiagnosticDescriptors.StartableNonAsyncMethod);

public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();

context.RegisterSymbolAction(Execute, SymbolKind.Method);
}

private static void Execute(SymbolAnalysisContext context)
{
if (context.Symbol is not IMethodSymbol methodSymbol)
return;

if (!Diagnostics.Startable.HasAttribute(methodSymbol))
return;

var diagnostics = Diagnostics.Startable.StartableNonAsyncMethod.Check(methodSymbol);

foreach (var diagnostic in diagnostics)
{
context.ReportDiagnostic(diagnostic);
}
}
}
Loading

0 comments on commit d35e7be

Please sign in to comment.